Compare commits

..

No commits in common. "master" and "2_5" have entirely different histories.
master ... 2_5

249 changed files with 74824 additions and 20749 deletions

4
.github/FUNDING.yml vendored
View File

@ -1,4 +0,0 @@
github:
- dvarrazzo
custom:
- "https://www.paypal.me/dvarrazzo"

View File

@ -1,23 +0,0 @@
---
name: Problem installing psycopg2
about: Report a case in which psycopg2 failed to install on your platform
title: ''
labels: ''
assignees: ''
---
**This is a bug tracker**
If you have a question, such has "how do you do X with Python/PostgreSQL/psycopg2" please [write to the mailing list](https://lists.postgresql.org/manage/) or [open a question](https://github.com/psycopg/psycopg2/discussions) instead.
**Before opening this ticket, please confirm that:**
- [ ] I am running the latest version of pip, i.e. typing ``pip --version`` you get [this version](https://pypi.org/project/pip/).
- [ ] I have read the [installation documentation](https://www.psycopg.org/docs/install.html) and the [frequently asked questions](https://www.psycopg.org/docs/faq.html)
- [ ] If install failed, I typed `pg_config` on the command line and I obtained an output instead of an error.
**Please complete the following information:**
- OS:
- Psycopg version:
- Python version:
- PostgreSQL version:
- pip version

View File

@ -1,27 +0,0 @@
---
name: Problem using psycopg2
about: Report a case in which psycopg2 is not working as expected
title: ''
labels: ''
assignees: ''
---
**This is a bug tracker**
If you have a question, such has "how do you do X with Python/PostgreSQL/psycopg2" please [write to the mailing list](https://lists.postgresql.org/manage/) or [open a question](https://github.com/psycopg/psycopg2/discussions) instead.
**Please complete the following information:**
- OS:
- Psycopg version:
- Python version:
- PostgreSQL version:
- pip version
**Describe the bug**
Please let us know:
1: what you did
2: what you expected to happen
3: what happened instead
If possible, provide a script reproducing the issue.

View File

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

View File

@ -1,18 +0,0 @@
name: Build documentation
on:
push:
branches:
# This should match the DOC_BRANCH value in the psycopg-website Makefile
- master
jobs:
docs:
runs-on: ubuntu-latest
steps:
- name: Trigger docs build
uses: peter-evans/repository-dispatch@v3
with:
repository: psycopg/psycopg-website
event-type: psycopg2-commit
token: ${{ secrets.ACCESS_TOKEN }}

View File

@ -1,266 +0,0 @@
---
name: Build packages
on:
- workflow_dispatch
env:
PIP_BREAK_SYSTEM_PACKAGES: "1"
LIBPQ_VERSION: "16.0"
OPENSSL_VERSION: "1.1.1w"
jobs:
sdist: # {{{
if: true
strategy:
fail-fast: false
matrix:
include:
- package_name: psycopg2
- package_name: psycopg2-binary
runs-on: ubuntu-latest
steps:
- name: Checkout repos
uses: actions/checkout@v4
- name: Build sdist
run: ./scripts/build/build_sdist.sh
env:
PACKAGE_NAME: ${{ matrix.package_name }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: sdist-${{ matrix.package_name }}
path: |
dist/*.tar.gz
env:
PSYCOPG2_TESTDB: postgres
PSYCOPG2_TESTDB_HOST: 172.17.0.1
PSYCOPG2_TESTDB_USER: postgres
PSYCOPG2_TESTDB_PASSWORD: password
PSYCOPG2_TEST_FAST: 1
services:
postgresql:
image: postgres:16
env:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
# }}}
linux: # {{{
if: true
strategy:
fail-fast: false
matrix:
platform: [manylinux, musllinux]
arch: [x86_64, i686, aarch64, ppc64le]
pyver: [cp38, cp39, cp310, cp311, cp312, cp313]
runs-on: ubuntu-latest
steps:
- name: Checkout repos
uses: actions/checkout@v4
- name: Set up QEMU for multi-arch build
uses: docker/setup-qemu-action@v3
- name: Cache libpq build
uses: actions/cache@v4
with:
path: /tmp/libpq.build
key: libpq-${{ env.LIBPQ_VERSION }}-${{ matrix.platform }}-${{ matrix.arch }}
- name: Build wheels
uses: pypa/cibuildwheel@v2.23.2
env:
CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
CIBW_MANYLINUX_I686_IMAGE: manylinux2014
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014
CIBW_MANYLINUX_PPC64LE_IMAGE: manylinux2014
CIBW_BUILD: ${{matrix.pyver}}-${{matrix.platform}}_${{matrix.arch}}
CIBW_ARCHS_LINUX: auto aarch64 ppc64le
CIBW_BEFORE_ALL_LINUX: ./scripts/build/wheel_linux_before_all.sh
CIBW_REPAIR_WHEEL_COMMAND: >-
./scripts/build/strip_wheel.sh {wheel}
&& auditwheel repair -w {dest_dir} {wheel}
CIBW_TEST_COMMAND: >-
export PYTHONPATH={project} &&
python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')"
CIBW_ENVIRONMENT_PASS_LINUX: LIBPQ_VERSION OPENSSL_VERSION
CIBW_ENVIRONMENT: >-
PACKAGE_NAME=psycopg2-binary
LIBPQ_BUILD_PREFIX=/host/tmp/libpq.build
PATH="$LIBPQ_BUILD_PREFIX/bin:$PATH"
LD_LIBRARY_PATH="$LIBPQ_BUILD_PREFIX/lib:$LIBPQ_BUILD_PREFIX/lib64"
PSYCOPG2_TESTDB=postgres
PSYCOPG2_TESTDB_HOST=172.17.0.1
PSYCOPG2_TESTDB_USER=postgres
PSYCOPG2_TESTDB_PASSWORD=password
PSYCOPG2_TEST_FAST=1
- uses: actions/upload-artifact@v4
with:
name: linux-${{matrix.pyver}}-${{matrix.platform}}_${{matrix.arch}}
path: ./wheelhouse/*.whl
services:
postgresql:
image: postgres:16
env:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
# }}}
macos: # {{{
runs-on: macos-latest
if: true
strategy:
fail-fast: false
matrix:
# These archs require an Apple M1 runner: [arm64, universal2]
arch: [x86_64, arm64]
pyver: [cp39, cp310, cp311, cp312, cp313]
steps:
- name: Checkout repos
uses: actions/checkout@v4
- name: Cache libpq build
uses: actions/cache@v4
with:
path: /tmp/libpq.build
key: libpq-${{ env.LIBPQ_VERSION }}-macos-${{ matrix.arch }}
- name: Build wheels
uses: pypa/cibuildwheel@v2.23.2
env:
CIBW_BUILD: ${{matrix.pyver}}-macosx_${{matrix.arch}}
CIBW_ARCHS_MACOS: ${{matrix.arch}}
MACOSX_ARCHITECTURE: ${{matrix.arch}}
CIBW_BEFORE_ALL_MACOS: ./scripts/build/wheel_macos_before_all.sh
CIBW_TEST_COMMAND: >-
export PYTHONPATH={project} &&
python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')"
CIBW_ENVIRONMENT: >-
PG_VERSION=16
PACKAGE_NAME=psycopg2-binary
PSYCOPG2_TESTDB=postgres
PATH="/tmp/libpq.build/bin:$PATH"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: macos-${{matrix.pyver}}-macos-${{matrix.arch}}
path: ./wheelhouse/*.whl
# }}}
windows: # {{{
runs-on: windows-latest
if: true
strategy:
fail-fast: false
matrix:
arch: [win_amd64]
pyver: [cp38, cp39, cp310, cp311, cp312, cp313]
package_name: [psycopg2, psycopg2-binary]
defaults:
run:
shell: bash
steps:
# there are some other libpq in PATH
- name: Drop spurious libpq in the path
run: rm -rf c:/tools/php C:/Strawberry/c/bin
- name: Checkout repo
uses: actions/checkout@v4
- name: Start PostgreSQL service for test
run: |
$PgSvc = Get-Service "postgresql*"
Set-Service $PgSvc.Name -StartupType manual
$PgSvc.Start()
shell: powershell
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v7
with:
script: |
const path = require('path')
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
core.addPath(path.join(process.env.VCPKG_INSTALLATION_ROOT, 'installed/x64-windows-release/lib'));
core.addPath(path.join(process.env.VCPKG_INSTALLATION_ROOT, 'installed/x64-windows-release/bin'));
- name: Create the binary package source tree
run: >-
sed -i 's/^setup(name="psycopg2"/setup(name="${{matrix.package_name}}"/'
setup.py
if: ${{ matrix.package_name != 'psycopg2' }}
- name: Build wheels
uses: pypa/cibuildwheel@v2.23.2
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" # cache vcpkg
CIBW_BUILD: ${{matrix.pyver}}-${{matrix.arch}}
CIBW_ARCHS_WINDOWS: AMD64 x86
CIBW_BEFORE_BUILD_WINDOWS: '.\scripts\build\wheel_win32_before_build.bat'
CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: >-
delvewheel repair -w {dest_dir}
--no-mangle "libiconv-2.dll;libwinpthread-1.dll" {wheel}
CIBW_TEST_COMMAND: >-
set PYTHONPATH={project} &&
python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')"
# Note: no fast test because we don't run Windows tests
CIBW_ENVIRONMENT_WINDOWS: >-
PSYCOPG2_TESTDB=postgres
PSYCOPG2_TESTDB_USER=postgres
PSYCOPG2_TESTDB_HOST=localhost
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: windows-${{ matrix.package_name }}-${{matrix.pyver}}-${{matrix.arch}}
path: ./wheelhouse/*.whl
# }}}
merge: # {{{
runs-on: ubuntu-latest
needs:
- sdist
- linux
- macos
- windows
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: psycopg2-artifacts
delete-merged: true
# }}}

View File

@ -1,79 +0,0 @@
name: Tests
env:
PIP_BREAK_SYSTEM_PACKAGES: "1"
on:
push:
pull_request:
jobs:
linux:
runs-on: ubuntu-latest
if: true
strategy:
fail-fast: false
matrix:
include:
- {python: "3.8", postgres: "12"}
- {python: "3.9", postgres: "13"}
- {python: "3.10", postgres: "14"}
- {python: "3.11", postgres: "15"}
- {python: "3.12", postgres: "16"}
- {python: "3.13", postgres: "17"}
# Opposite extremes of the supported Py/PG range, other architecture
- {python: "3.8", postgres: "17", architecture: "x86"}
- {python: "3.9", postgres: "16", architecture: "x86"}
- {python: "3.10", postgres: "15", architecture: "x86"}
- {python: "3.11", postgres: "14", architecture: "x86"}
- {python: "3.12", postgres: "13", architecture: "x86"}
- {python: "3.13", postgres: "12", architecture: "x86"}
env:
PSYCOPG2_TESTDB: postgres
PSYCOPG2_TESTDB_HOST: 127.0.0.1
PSYCOPG2_TESTDB_USER: postgres
PSYCOPG2_TESTDB_PASSWORD: password
services:
postgresql:
image: postgres:${{ matrix.postgres }}
env:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
# Can enable to test an unreleased libpq version.
- name: install libpq 16
if: false
run: |
set -x
rel=$(lsb_release -c -s)
echo "deb http://apt.postgresql.org/pub/repos/apt ${rel}-pgdg main 16" \
| sudo tee -a /etc/apt/sources.list.d/pgdg.list
sudo apt-get -qq update
pqver=$(apt-cache show libpq5 | grep ^Version: | head -1 \
| awk '{print $2}')
sudo apt-get -qq -y install "libpq-dev=${pqver}" "libpq5=${pqver}"
- name: Install tox
run: pip install "tox < 4"
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Run tests
env:
MATRIX_PYTHON: ${{ matrix.python }}
run: tox -e ${MATRIX_PYTHON%-dev}
timeout-minutes: 5

13
.gitignore vendored
View File

@ -4,15 +4,10 @@ MANIFEST
*.pidb *.pidb
*.pyc *.pyc
*.sw[po] *.sw[po]
*.egg-info/
dist/* dist/*
/build build/*
doc/src/_build/*
doc/html/*
doc/psycopg2.txt
env env
env?
.idea
.tox .tox
.vscode/
/rel
/wheels
/packages
/wheelhouse

13
.travis.yml Normal file
View File

@ -0,0 +1,13 @@
language: python
python:
- 2.6
- 2.7
before_script:
- psql -c 'create database psycopg2_test;' -U postgres
install:
- python setup.py install
script: make check

105
INSTALL
View File

@ -1,4 +1,103 @@
Installation instructions are included in the docs. 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 libary 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
Please check the 'doc/src/install.rst' file or online at
<https://www.psycopg.org/docs/install.html>.

15
LICENSE
View File

@ -1,5 +1,5 @@
psycopg2 and the LGPL psycopg2 and the LGPL
--------------------- =====================
psycopg2 is free software: you can redistribute it and/or modify it psycopg2 is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published under the terms of the GNU Lesser General Public License as published
@ -25,14 +25,19 @@ statement from all source files in the program, then also delete it here.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public License
along with psycopg2 (see the doc/ directory.) along with psycopg2 (see the doc/ directory.)
If not, see <https://www.gnu.org/licenses/>. If not, see <http://www.gnu.org/licenses/>.
Alternative licenses Alternative licenses
-------------------- ====================
The following BSD-like license applies (at your option) to the files following If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e.,
the pattern ``psycopg/adapter*.{h,c}`` and ``psycopg/microprotocol*.{h,c}``: every file inside the ZPsycopgDA directory) user the ZPL license as
published on the Zope web site, http://www.zope.org/Resources/ZPL.
Also, the following BSD-like license applies (at your option) to the
files following the pattern psycopg/adapter*.{h,c} and
psycopg/microprotocol*.{h,c}:
Permission is granted to anyone to use this software for any purpose, Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it including commercial applications, and to alter it and redistribute it

View File

@ -1,9 +1,16 @@
recursive-include psycopg *.c *.h *.manifest recursive-include psycopg *.c *.h *.manifest
recursive-include lib *.py recursive-include lib *.py
recursive-include tests *.py recursive-include tests *.py
include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt recursive-include ZPsycopgDA *.py *.gif *.dtml
include doc/Makefile doc/requirements.txt recursive-include psycopg2da *
recursive-include examples *.py somehackers.jpg whereareyou.jpg
recursive-include debian *
recursive-include doc README HACKING SUCCESS COPYING* ChangeLog-1.x pep-0249.txt
recursive-include doc *.txt *.html *.css *.js Makefile
recursive-include doc/src *.rst *.py *.css Makefile recursive-include doc/src *.rst *.py *.css Makefile
recursive-include doc/html *
prune doc/src/_build
recursive-include scripts *.py *.sh recursive-include scripts *.py *.sh
include AUTHORS README.rst INSTALL LICENSE NEWS include scripts/maketypes.sh scripts/buildtypes.py
include MANIFEST.in setup.py setup.cfg Makefile include AUTHORS README INSTALL LICENSE NEWS ChangeLog
include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile

View File

@ -6,11 +6,12 @@
# #
# Build the documentation:: # Build the documentation::
# #
# make env (once) # make env
# make docs # make docs
# #
# Create a source package:: # Create a source package::
# #
# make env # required to build the documentation
# make sdist # make sdist
# #
# Run the test:: # Run the test::
@ -20,6 +21,9 @@
PYTHON := python$(PYTHON_VERSION) PYTHON := python$(PYTHON_VERSION)
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])') PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
BUILD_DIR = $(shell pwd)/build/lib.$(PYTHON_VERSION) 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_C := $(wildcard psycopg/*.c psycopg/*.h)
SOURCE_PY := $(wildcard lib/*.py) SOURCE_PY := $(wildcard lib/*.py)
@ -29,7 +33,8 @@ SOURCE := $(SOURCE_C) $(SOURCE_PY) $(SOURCE_TESTS) $(SOURCE_DOC)
PACKAGE := $(BUILD_DIR)/psycopg2 PACKAGE := $(BUILD_DIR)/psycopg2
PLATLIB := $(PACKAGE)/_psycopg.so PLATLIB := $(PACKAGE)/_psycopg.so
PURELIB := $(patsubst lib/%,$(PACKAGE)/%,$(SOURCE_PY)) PURELIB := $(patsubst lib/%,$(PACKAGE)/%,$(SOURCE_PY)) \
$(patsubst tests/%,$(PACKAGE)/tests/%,$(SOURCE_TESTS))
BUILD_OPT := --build-lib=$(BUILD_DIR) BUILD_OPT := --build-lib=$(BUILD_DIR)
BUILD_EXT_OPT := --build-lib=$(BUILD_DIR) BUILD_EXT_OPT := --build-lib=$(BUILD_DIR)
@ -42,7 +47,10 @@ endif
VERSION := $(shell grep PSYCOPG_VERSION setup.py | head -1 | sed -e "s/.*'\(.*\)'/\1/") VERSION := $(shell grep PSYCOPG_VERSION setup.py | head -1 | sed -e "s/.*'\(.*\)'/\1/")
SDIST := dist/psycopg2-$(VERSION).tar.gz SDIST := dist/psycopg2-$(VERSION).tar.gz
.PHONY: check clean 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 default: package
@ -50,20 +58,36 @@ all: package sdist
package: $(PLATLIB) $(PURELIB) package: $(PLATLIB) $(PURELIB)
docs: docs-html docs: docs-html docs-txt
docs-html: doc/html/genindex.html docs-html: doc/html/genindex.html
docs-txt: doc/psycopg2.txt
# for PyPI documentation # for PyPI documentation
docs-zip: doc/docs.zip docs-zip: doc/docs.zip
sdist: $(SDIST) sdist: $(SDIST)
env: # The environment is currently required to build the documentation.
$(MAKE) -C doc $@ # 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
check: check:
PYTHONPATH=$(BUILD_DIR) $(PYTHON) -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose PYTHONPATH=$(BUILD_DIR):$(PYTHONPATH) $(PYTHON) -c "from psycopg2 import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
testdb: testdb:
@echo "* Creating $(TESTDB)" @echo "* Creating $(TESTDB)"
@ -89,16 +113,24 @@ $(PACKAGE)/tests/%.py: tests/%.py
$(PYTHON) setup.py build_py $(BUILD_OPT) $(PYTHON) setup.py build_py $(BUILD_OPT)
touch $@ touch $@
$(SDIST): $(SOURCE) $(SDIST): docs MANIFEST $(SOURCE)
$(PYTHON) setup.py sdist $(SDIST_OPT) $(PYTHON) setup.py sdist $(SDIST_OPT)
MANIFEST: MANIFEST.in $(SOURCE)
# Run twice as MANIFEST.in includes MANIFEST
$(PYTHON) setup.py sdist --manifest-only
$(PYTHON) setup.py sdist --manifest-only
# docs depend on the build as it partly use introspection. # docs depend on the build as it partly use introspection.
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC) doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
$(MAKE) -C doc html PYTHONPATH=$(ENV_LIB):$(BUILD_DIR) $(MAKE) SPHINXBUILD=$(ENV_BIN)/sphinx-build -C doc html
doc/psycopg2.txt: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
PYTHONPATH=$(ENV_LIB):$(BUILD_DIR) $(MAKE) SPHINXBUILD=$(ENV_BIN)/sphinx-build -C doc text
doc/docs.zip: doc/html/genindex.html doc/docs.zip: doc/html/genindex.html
(cd doc/html && zip -r ../docs.zip *) (cd doc/html && zip -r ../docs.zip *)
clean: clean:
rm -rf build rm -rf build MANIFEST
$(MAKE) -C doc clean $(MAKE) -C doc clean

613
NEWS
View File

@ -1,591 +1,3 @@
Current release
---------------
What's new in psycopg 2.9.10
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Add support for Python 3.13.
- Receive notifications on commit (:ticket:`#1728`).
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
PostgreSQL 17.
- Drop support for Python 3.7.
What's new in psycopg 2.9.9
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Add support for Python 3.12.
- Drop support for Python 3.6.
What's new in psycopg 2.9.8
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Wheel package bundled with PostgreSQL 16 libpq in order to add support for
recent features, such as ``sslcertmode``.
What's new in psycopg 2.9.7
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fix propagation of exceptions raised during module initialization
(:ticket:`#1598`).
- Fix building when pg_config returns an empty string (:ticket:`#1599`).
- Wheel package bundled with OpenSSL 1.1.1v.
What's new in psycopg 2.9.6
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Package manylinux 2014 for aarch64 and ppc64le platforms, in order to
include libpq 15 in the binary package (:ticket:`#1396`).
- Wheel package bundled with OpenSSL 1.1.1t.
What's new in psycopg 2.9.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Add support for Python 3.11.
- Add support for rowcount in MERGE statements in binary packages
(:ticket:`#1497`).
- Wheel package bundled with OpenSSL 1.1.1r and PostgreSQL 15 libpq.
What's new in psycopg 2.9.4
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fix `~psycopg2.extras.register_composite()`,
`~psycopg2.extras.register_range()` with customized :sql:`search_path`
(:ticket:`#1487`).
- Handle correctly composite types with names or in schemas requiring escape.
- Find ``pg_service.conf`` file in the ``/etc/postgresql-common`` directory in
binary packages (:ticket:`#1365`).
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
PostgreSQL 15.
- Wheel package bundled with OpenSSL 1.1.1q and PostgreSQL 14.4 libpq.
What's new in psycopg 2.9.3
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Alpine (musl) wheels now available (:ticket:`#1392`).
- macOS arm64 (Apple M1) wheels now available (:ticket:`1482`).
What's new in psycopg 2.9.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Raise `ValueError` for dates >= Y10k (:ticket:`#1307`).
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
PostgreSQL 14.
- Add preliminary support for Python 3.11 (:tickets:`#1376, #1386`).
- Wheel package bundled with OpenSSL 1.1.1l and PostgreSQL 14.1 libpq
(:ticket:`#1388`).
What's new in psycopg 2.9.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fix regression with named `~psycopg2.sql.Placeholder` (:ticket:`#1291`).
What's new in psycopg 2.9
-------------------------
- ``with connection`` starts a transaction on autocommit transactions too
(:ticket:`#941`).
- Timezones with fractional minutes are supported on Python 3.7 and following
(:ticket:`#1272`).
- Escape table and column names in `~cursor.copy_from()` and
`~cursor.copy_to()`.
- Connection exceptions with sqlstate ``08XXX`` reclassified as
`~psycopg2.OperationalError` (a subclass of the previously used
`~psycopg2.DatabaseError`) (:ticket:`#1148`).
- Include library dirs required from libpq to work around MacOS build problems
(:ticket:`#1200`).
Other changes:
- Dropped support for Python 2.7, 3.4, 3.5 (:tickets:`#1198, #1000, #1197`).
- Dropped support for mx.DateTime.
- Use `datetime.timezone` objects by default in datetime objects instead of
`~psycopg2.tz.FixedOffsetTimezone`.
- The `psycopg2.tz` module is deprecated and scheduled to be dropped in the
next major release.
- Provide :pep:`599` wheels packages (manylinux2014 tag) for i686 and x86_64
platforms.
- Provide :pep:`600` wheels packages (manylinux_2_24 tag) for aarch64 and
ppc64le platforms.
- Wheel package bundled with OpenSSL 1.1.1k and PostgreSQL 13.3 libpq.
- Build system for Linux/MacOS binary packages moved to GitHub Actions.
What's new in psycopg 2.8.7
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Accept empty params as `~psycopg2.connect()` (:ticket:`#1250`).
- Fix attributes refcount in `Column` initialisation (:ticket:`#1252`).
- Allow re-initialisation of static variables in the C module (:ticket:`#1267`).
What's new in psycopg 2.8.6
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed memory leak changing connection encoding to the current one
(:ticket:`#1101`).
- Fixed search of mxDateTime headers in virtualenvs (:ticket:`#996`).
- Added missing values from errorcodes (:ticket:`#1133`).
- `cursor.query` reports the query of the last :sql:`COPY` operation too
(:ticket:`#1141`).
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
PostgreSQL 13.
- Added wheel packages for ARM architecture (:ticket:`#1125`).
- Wheel package bundled with OpenSSL 1.1.1g.
What's new in psycopg 2.8.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed use of `!connection_factory` and `!cursor_factory` together
(:ticket:`#1019`).
- Added support for `~logging.LoggerAdapter` in
`~psycopg2.extras.LoggingConnection` (:ticket:`#1026`).
- `~psycopg2.extensions.Column` objects in `cursor.description` can be sliced
(:ticket:`#1034`).
- Added AIX support (:ticket:`#1061`).
- Fixed `~copy.copy()` of `~psycopg2.extras.DictCursor` rows (:ticket:`#1073`).
What's new in psycopg 2.8.4
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed building with Python 3.8 (:ticket:`#854`).
- Don't swallow keyboard interrupts on connect when a password is specified
in the connection string (:ticket:`#898`).
- Don't advance replication cursor when the message wasn't confirmed
(:ticket:`#940`).
- Fixed inclusion of ``time.h`` on linux (:ticket:`#951`).
- Fixed int overflow for large values in `~psycopg2.extensions.Column.table_oid`
and `~psycopg2.extensions.Column.type_code` (:ticket:`#961`).
- `~psycopg2.errorcodes` map and `~psycopg2.errors` classes updated to
PostgreSQL 12.
- Wheel package bundled with OpenSSL 1.1.1d and PostgreSQL at least 11.4.
What's new in psycopg 2.8.3
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Added *interval_status* parameter to
`~psycopg2.extras.ReplicationCursor.start_replication()` method and other
facilities to send automatic replication keepalives at periodic intervals
(:ticket:`#913`).
- Fixed namedtuples caching introduced in 2.8 (:ticket:`#928`).
What's new in psycopg 2.8.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed `~psycopg2.extras.RealDictCursor` when there are repeated columns
(:ticket:`#884`).
- Binary packages built with openssl 1.1.1b. Should fix concurrency problems
(:tickets:`#543, #836`).
What's new in psycopg 2.8.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed `~psycopg2.extras.RealDictRow` modifiability (:ticket:`#886`).
- Fixed "there's no async cursor" error polling a connection with no cursor
(:ticket:`#887`).
What's new in psycopg 2.8
-------------------------
New features:
- Added `~psycopg2.errors` module. Every PostgreSQL error is converted into
a specific exception class (:ticket:`#682`).
- Added `~psycopg2.extensions.encrypt_password()` function (:ticket:`#576`).
- Added `~psycopg2.extensions.BYTES` adapter to manage databases with mixed
encodings on Python 3 (:ticket:`#835`).
- Added `~psycopg2.extensions.Column.table_oid` and
`~psycopg2.extensions.Column.table_column` attributes on `cursor.description`
items (:ticket:`#661`).
- Added `connection.info` object to retrieve various PostgreSQL connection
information (:ticket:`#726`).
- Added `~connection.get_native_connection()` to expose the raw ``PGconn``
structure to C extensions via Capsule (:ticket:`#782`).
- Added `~connection.pgconn_ptr` and `~cursor.pgresult_ptr` to expose raw
C structures to Python and interact with libpq via ctypes (:ticket:`#782`).
- `~psycopg2.sql.Identifier` can represent qualified names in SQL composition
(:ticket:`#732`).
- Added `!ReplicationCursor`.\ `~psycopg2.extras.ReplicationCursor.wal_end`
attribute (:ticket:`#800`).
- Added *fetch* parameter to `~psycopg2.extras.execute_values()` function
(:ticket:`#813`).
- `!str()` on `~psycopg2.extras.Range` produces a human-readable representation
(:ticket:`#773`).
- `~psycopg2.extras.DictCursor` and `~psycopg2.extras.RealDictCursor` rows
maintain columns order (:ticket:`#177`).
- Added `~psycopg2.extensions.Diagnostics.severity_nonlocalized` attribute on
the `~psycopg2.extensions.Diagnostics` object (:ticket:`#783`).
- More efficient `~psycopg2.extras.NamedTupleCursor` (:ticket:`#838`).
Bug fixes:
- Fixed connections occasionally broken by the unrelated use of the
multiprocessing module (:ticket:`#829`).
- Fixed async communication blocking if results are returned in different
chunks, e.g. with notices interspersed to the results (:ticket:`#856`).
- Fixed adaptation of numeric subclasses such as `~enum.IntEnum`
(:ticket:`#591`).
Other changes:
- Dropped support for Python 2.6, 3.2, 3.3.
- Dropped `psycopg1` module.
- Dropped deprecated `!register_tstz_w_secs()` (was previously a no-op).
- Dropped deprecated `!PersistentConnectionPool`. This pool class was mostly
designed to interact with Zope. Use `!ZPsycopgDA.pool` instead.
- Binary packages no longer installed by default. The 'psycopg2-binary'
package must be used explicitly.
- Dropped `!PSYCOPG_DISPLAY_SIZE` build parameter.
- Dropped support for mxDateTime as the default date and time adapter.
mxDatetime support continues to be available as an alternative to Python's
builtin datetime.
- No longer use 2to3 during installation for Python 2 & 3 compatibility. All
source files are now compatible with Python 2 & 3 as is.
- The `!psycopg2.test` package is no longer installed by ``python setup.py
install``.
- Wheel package bundled with OpenSSL 1.0.2r and PostgreSQL 11.2 libpq.
What's new in psycopg 2.7.7
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Cleanup of the cursor results assignment code, which might have solved
double free and inconsistencies in concurrent usage (:tickets:`#346, #384`).
- Wheel package bundled with OpenSSL 1.0.2q.
What's new in psycopg 2.7.6.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed binary package broken on OS X 10.12 (:ticket:`#807`).
- Wheel package bundled with PostgreSQL 11.1 libpq.
What's new in psycopg 2.7.6
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Close named cursors if exist, even if `~cursor.execute()` wasn't called
(:ticket:`#746`).
- Fixed building on modern FreeBSD versions with Python 3.7 (:ticket:`#755`).
- Fixed hang trying to :sql:`COPY` via `~cursor.execute()` in asynchronous
connections (:ticket:`#781`).
- Fixed adaptation of arrays of empty arrays (:ticket:`#788`).
- Fixed segfault accessing the connection's `~connection.readonly` and
`~connection.deferrable` attributes repeatedly (:ticket:`#790`).
- `~psycopg2.extras.execute_values()` accepts `~psycopg2.sql.Composable`
objects (:ticket:`#794`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 11.
- Wheel package bundled with PostgreSQL 10.5 libpq and OpenSSL 1.0.2p.
What's new in psycopg 2.7.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Allow non-ascii chars in namedtuple fields (regression introduced fixing
:ticket:`#211`).
- Fixed adaptation of arrays of arrays of nulls (:ticket:`#325`).
- Fixed building on Solaris 11 and derivatives such as SmartOS and illumos
(:ticket:`#677`).
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
- Allow string subclasses in connection and other places (:ticket:`#679`).
- Don't raise an exception closing an unused named cursor (:ticket:`#716`).
- Wheel package bundled with PostgreSQL 10.4 libpq and OpenSSL 1.0.2o.
What's new in psycopg 2.7.4
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Moving away from installing the wheel package by default.
Packages installed from wheel raise a warning on import. Added package
``psycopg2-binary`` to install from wheel instead (:ticket:`#543`).
- Convert fields names into valid Python identifiers in
`~psycopg2.extras.NamedTupleCursor` (:ticket:`#211`).
- Fixed Solaris 10 support (:ticket:`#532`).
- `cursor.mogrify()` can be called on closed cursors (:ticket:`#579`).
- Fixed setting session characteristics in corner cases on autocommit
connections (:ticket:`#580`).
- Fixed `~psycopg2.extras.MinTimeLoggingCursor` on Python 3 (:ticket:`#609`).
- Fixed parsing of array of points as floats (:ticket:`#613`).
- Fixed `~psycopg2.__libpq_version__` building with libpq >= 10.1
(:ticket:`#632`).
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING`
statements (:ticket:`#633`).
- Fixed compatibility problem with pypy3 (:ticket:`#649`).
- Wheel packages bundled with PostgreSQL 10.1 libpq and OpenSSL 1.0.2n.
- Wheel packages for Python 2.6 no more available (support dropped from
wheel building infrastructure).
What's new in psycopg 2.7.3.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Wheel package bundled with PostgreSQL 10.0 libpq and OpenSSL 1.0.2l
(:tickets:`#601, #602`).
What's new in psycopg 2.7.3.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Dropped libresolv from wheel package to avoid incompatibility with
glibc 2.26 (wheels ticket #2).
What's new in psycopg 2.7.3
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Restored default :sql:`timestamptz[]` typecasting to Python `!datetime`.
Regression introduced in Psycopg 2.7.2 (:ticket:`#578`).
What's new in psycopg 2.7.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed inconsistent state in externally closed connections
(:tickets:`#263, #311, #443`). Was fixed in 2.6.2 but not included in
2.7 by mistake.
- Fixed Python exceptions propagation in green callback (:ticket:`#410`).
- Don't display the password in `connection.dsn` when the connection
string is specified as an URI (:ticket:`#528`).
- Return objects with timezone parsing "infinity" :sql:`timestamptz`
(:ticket:`#536`).
- Dropped dependency on VC9 runtime on Windows binary packages
(:ticket:`#541`).
- Fixed segfault in `~connection.lobject()` when *mode*\=\ `!None`
(:ticket:`#544`).
- Fixed `~connection.lobject()` keyword argument *lobject_factory*
(:ticket:`#545`).
- Fixed `~psycopg2.extras.ReplicationCursor.consume_stream()`
*keepalive_interval* argument (:ticket:`#547`).
- Maybe fixed random import error on Python 3.6 in multiprocess
environment (:ticket:`#550`).
- Fixed random `!SystemError` upon receiving abort signal (:ticket:`#551`).
- Accept `~psycopg2.sql.Composable` objects in
`~psycopg2.extras.ReplicationCursor.start_replication_expert()`
(:ticket:`#554`).
- Parse intervals returned as microseconds from Redshift (:ticket:`#558`).
- Added `~psycopg2.extras.Json` `!prepare()` method to consider connection
params when adapting (:ticket:`#562`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 10 beta 1.
What's new in psycopg 2.7.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Ignore `!None` arguments passed to `~psycopg2.connect()` and
`~psycopg2.extensions.make_dsn()` (:ticket:`#517`).
- OpenSSL upgraded from major version 0.9.8 to 1.0.2 in the Linux wheel
packages (:ticket:`#518`).
- Fixed build with libpq versions < 9.3 (:ticket:`#520`).
What's new in psycopg 2.7
-------------------------
New features:
- Added `~psycopg2.sql` module to generate SQL dynamically (:ticket:`#308`).
- Added :ref:`replication-support` (:ticket:`#322`). Main authors are
Oleksandr Shulgin and Craig Ringer, who deserve a huge thank you.
- Added `~psycopg2.extensions.parse_dsn()` and
`~psycopg2.extensions.make_dsn()` functions (:tickets:`#321, #363`).
`~psycopg2.connect()` now can take both *dsn* and keyword arguments, merging
them together.
- Added `~psycopg2.__libpq_version__` and
`~psycopg2.extensions.libpq_version()` to inspect the version of the
``libpq`` library the module was bundled 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`).
- Adapt network types to `ipaddress` objects when available. When not
enabled, convert arrays of network types to lists by default. The old `!Inet`
adapter is deprecated (:tickets:`#317, #343, #387`).
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
- Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`).
- `~cursor.callproc()` now accepts a dictionary of parameters (:ticket:`#381`).
- Give precedence to `!__conform__()` over superclasses to choose an object
adapter (:ticket:`#456`).
- Using Python C API decoding functions and codecs caching for faster
unicode encoding/decoding (:ticket:`#473`).
- `~cursor.executemany()` slowness addressed by
`~psycopg2.extras.execute_batch()` and `~psycopg2.extras.execute_values()`
(:ticket:`#491`).
- Added ``async_`` as an alias for ``async`` to support Python 3.7 where
``async`` will become a keyword (:ticket:`#495`).
- Unless in autocommit, do not use :sql:`default_transaction_*` settings to
control the session characteristics as it may create problems with external
connection pools such as pgbouncer; use :sql:`BEGIN` options instead
(:ticket:`#503`).
- `~connection.isolation_level` is now writable and entirely separated from
`~connection.autocommit`; added `~connection.readonly`,
`~connection.deferrable` writable attributes.
Bug fixes:
- Throw an exception trying to pass ``NULL`` chars as parameters
(:ticket:`#420`).
- Fixed error caused by missing decoding `~psycopg2.extras.LoggingConnection`
(:ticket:`#483`).
- Fixed integer overflow in :sql:`interval` seconds (:ticket:`#512`).
- Make `~psycopg2.extras.Range` objects picklable (:ticket:`#462`).
- Fixed version parsing and building with PostgreSQL 10 (:ticket:`#489`).
Other changes:
- Dropped support for Python 2.5 and 3.1.
- Dropped support for client library older than PostgreSQL 9.1 (but older
server versions are still supported).
- `~connection.isolation_level` doesn't read from the database but will return
`~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT` if no value was set on the
connection.
- Empty arrays no more converted into lists if they don't have a type attached
(:ticket:`#506`)
What's new in psycopg 2.6.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed inconsistent state in externally closed connections
(:tickets:`#263, #311, #443`).
- Report the server response status on errors (such as :ticket:`#281`).
- Raise `!NotSupportedError` on unhandled server response status
(:ticket:`#352`).
- Allow overriding string adapter encoding with no connection (:ticket:`#331`).
- The `~psycopg2.extras.wait_select` callback allows interrupting a
long-running query in an interactive shell using :kbd:`Ctrl-C`
(:ticket:`#333`).
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`).
- Allow adapting bytes using `~psycopg2.extensions.QuotedString` on Python 3
(:ticket:`#365`).
- Added support for setuptools/wheel (:ticket:`#370`).
- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`).
- Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`).
- Fixed `!read()` exception propagation in copy_from (:ticket:`#412`).
- Fixed possible NULL TZ decref (:ticket:`#424`).
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.5.
What's new in psycopg 2.6.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Lists consisting of only `None` are escaped correctly (:ticket:`#285`).
- Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`).
- Correctly unlock the connection after error in flush (:ticket:`#294`).
- Fixed `!MinTimeLoggingCursor.callproc()` (:ticket:`#309`).
- Added support for MSVC 2015 compiler (:ticket:`#350`).
What's new in psycopg 2.6
-------------------------
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 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Work around `pip issue #1630 <https://github.com/pypa/pip/issues/1630>`__
making installation via ``pip -e git+url`` impossible (:ticket:`#18`).
- Copy operations correctly set the `cursor.rowcount` attribute
(:ticket:`#180`).
- It is now possible to call `get_transaction_status()` on closed connections.
- Fixed unsafe access to object names causing assertion failures in
Python 3 debug builds (:ticket:`#188`).
- Mark the connection closed if found broken on `poll()` (from :ticket:`#192`
discussion)
- Fixed handling of dsn and closed attributes in connection subclasses
failing to connect (from :ticket:`#192` discussion).
- Added arbitrary but stable order to `Range` objects, thanks to
Chris Withers (:ticket:`#193`).
- Avoid blocking async connections on connect (:ticket:`#194`). Thanks to
Adam Petrovich for the bug report and diagnosis.
- Don't segfault using poorly defined cursor subclasses which forgot to call
the superclass init (:ticket:`#195`).
- Mark the connection closed when a Socket connection is broken, as it
happens for TCP connections instead (:ticket:`#196`).
- Fixed overflow opening a lobject with an oid not fitting in a signed int
(:ticket:`#203`).
- Fixed handling of explicit default ``cursor_factory=None`` in
`connection.cursor()` (:ticket:`#210`).
- Fixed possible segfault in named cursors creation.
- Fixed debug build on Windows, thanks to James Emerton.
What's new in psycopg 2.5.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed segfault pickling the exception raised on connection error
(:ticket:`#170`).
- Meaningful connection errors report a meaningful message, thanks to
Alexey Borzenkov (:ticket:`#173`).
- Manually creating `lobject` with the wrong parameter doesn't segfault
(:ticket:`#187`).
What's new in psycopg 2.5.1
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed build on Solaris 10 and 11 where the round() function is already
declared (:ticket:`#146`).
- Fixed comparison of `Range` with non-range objects (:ticket:`#164`).
Thanks to Chris Withers for the patch.
- Fixed double-free on connection dealloc (:ticket:`#166`). Thanks to
Gangadharan S.A. for the report and fix suggestion.
What's new in psycopg 2.5 What's new in psycopg 2.5
------------------------- -------------------------
@ -621,7 +33,7 @@ Other changes:
- Dropped support for Python 2.4. Please use Psycopg 2.4.x if you need it. - Dropped support for Python 2.4. Please use Psycopg 2.4.x if you need it.
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.2. - `~psycopg2.errorcodes` map updated to PostgreSQL 9.2.
- Dropped Zope adapter from source repository. ZPsycopgDA now has its own - Dropped Zope adapter from source repository. ZPsycopgDA now has its own
project at <https://github.com/psycopg/ZPsycopgDA>. project at <http://github.com/psycopg/ZPsycopgDA>.
What's new in psycopg 2.4.6 What's new in psycopg 2.4.6
@ -639,7 +51,7 @@ What's new in psycopg 2.4.6
- 'register_hstore()', 'register_composite()', 'tpc_recover()' work with - 'register_hstore()', 'register_composite()', 'tpc_recover()' work with
RealDictConnection and Cursor (:ticket:`#114`). RealDictConnection and Cursor (:ticket:`#114`).
- Fixed broken pool for Zope and connections re-init across ZSQL methods - 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 - connect() raises an exception instead of swallowing keyword arguments
when a connection string is specified as well (:ticket:`#131`). when a connection string is specified as well (:ticket:`#131`).
- Discard any result produced by 'executemany()' (:ticket:`#133`). - Discard any result produced by 'executemany()' (:ticket:`#133`).
@ -661,7 +73,7 @@ What's new in psycopg 2.4.5
- Error and its subclasses are picklable, useful for multiprocessing - Error and its subclasses are picklable, useful for multiprocessing
interaction (:ticket:`#90`). interaction (:ticket:`#90`).
- Better efficiency and formatting of timezone offset objects thanks - 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. - Fixed 'rownumber' during iteration on cursor subclasses.
Regression introduced in 2.4.4 (:ticket:`#100`). Regression introduced in 2.4.4 (:ticket:`#100`).
- Added support for 'inet' arrays. - Added support for 'inet' arrays.
@ -769,7 +181,7 @@ New features and changes:
ISO885916, LATIN10, SHIFT_JIS_2004. ISO885916, LATIN10, SHIFT_JIS_2004.
- Dropped repeated dictionary lookups with unicode query/parameters. - Dropped repeated dictionary lookups with unicode query/parameters.
- Improvements to the named cursors: - Improvements to the named cusors:
- More efficient iteration on named cursors, fetching 'itersize' - More efficient iteration on named cursors, fetching 'itersize'
records at time from the backend. records at time from the backend.
@ -832,7 +244,7 @@ Main new features:
- `dict` to `hstore` adapter and `hstore` to `dict` typecaster, using both - `dict` to `hstore` adapter and `hstore` to `dict` typecaster, using both
9.0 and pre-9.0 syntax. 9.0 and pre-9.0 syntax.
- Two-phase commit protocol support as per DBAPI specification. - Two-phase commit protocol support as per DBAPI specification.
- Support for payload in notifications received from the backend. - Support for payload in notifications received from the backed.
- `namedtuple`-returning cursor. - `namedtuple`-returning cursor.
- Query execution cancel. - Query execution cancel.
@ -872,7 +284,7 @@ Bux fixes:
The old register_tstz_w_secs() function is deprecated and will raise a The old register_tstz_w_secs() function is deprecated and will raise a
warning if called. warning if called.
- Exceptions raised by the column iterator are propagated. - Exceptions raised by the column iterator are propagated.
- Exceptions raised by executemany() iterators are propagated. - Exceptions raised by executemany() interators are propagated.
What's new in psycopg 2.2.1 What's new in psycopg 2.2.1
@ -989,7 +401,7 @@ New features:
Bug fixes: Bug fixes:
- Fixed exception in setup.py. - Fixed exeception in setup.py.
- More robust detection of PostgreSQL development versions. - More robust detection of PostgreSQL development versions.
- Fixed exception in RealDictCursor, introduced in 2.0.10. - Fixed exception in RealDictCursor, introduced in 2.0.10.
@ -1245,7 +657,7 @@ What's new in psycopg 2.0 beta 7
What's new in psycopg 2.0 beta 6 What's new in psycopg 2.0 beta 6
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Support for named cursors. * Support for named cursors (see examples/fetch.py).
* Safer parsing of time intervals. * Safer parsing of time intervals.
@ -1275,7 +687,7 @@ What's new in psycopg 2.0 beta 5
* All classes have been renamed to exist in the psycopg2._psycopg module, * All classes have been renamed to exist in the psycopg2._psycopg module,
to fix problems with automatic documentation generators like epydoc. to fix problems with automatic documentation generators like epydoc.
* NOTIFY is correctly trapped. * NOTIFY is correctly trapped (see examples/notify.py for example code.)
What's new in psycopg 2.0 beta 4 What's new in psycopg 2.0 beta 4
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -1371,7 +783,7 @@ What's new in psycopg 1.99.11
* changed 'tuple_factory' cursor attribute name to 'row_factory'. * changed 'tuple_factory' cursor attribute name to 'row_factory'.
* the .cursor attribute is gone and connections and cursors are properly * the .cursor attribute is gone and connections and cursors are propely
gc-managed. gc-managed.
* fixes to the async core. * fixes to the async core.
@ -1392,7 +804,8 @@ What's new in psycopg 1.99.10
What's new in psycopg 1.99.9 What's new in psycopg 1.99.9
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Added simple pooling code (psycopg.pool module). * Added simple pooling code (psycopg.pool module); see the reworked
examples/threads.py for example code.
* Added DECIMAL typecaster to convert postgresql DECIMAL and NUMERIC * Added DECIMAL typecaster to convert postgresql DECIMAL and NUMERIC
types (i.e, all types with an OID of NUMERICOID.) Note that the types (i.e, all types with an OID of NUMERICOID.) Note that the
@ -1419,7 +832,7 @@ What's new in psycopg 1.99.8
* now cursors support .fileno() and .isready() methods, to be used in * now cursors support .fileno() and .isready() methods, to be used in
select() calls. select() calls.
* .copy_from() and .copy_in() methods are back in (still using the old * .copy_from() and .copy_in() methods are back in (still using the old
protocol, will be updated to use new one in next release.) protocol, will be updated to use new one in next releasae.)
* fixed memory corruption bug reported on win32 platform. * fixed memory corruption bug reported on win32 platform.
What's new in psycopg 1.99.7 What's new in psycopg 1.99.7

38
README Normal file
View File

@ -0,0 +1,38 @@
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.

View File

@ -1,80 +0,0 @@
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.
.. Note::
The psycopg2 package is still widely used and actively maintained, but it
is not expected to receive new features.
`Psycopg 3`__ is the evolution of psycopg2 and is where `new features are
being developed`__: if you are starting a new project you should probably
start from 3!
.. __: https://pypi.org/project/psycopg/
.. __: https://www.psycopg.org/psycopg3/docs/index.html
Documentation
-------------
Documentation is included in the ``doc`` directory and is `available online`__.
.. __: https://www.psycopg.org/docs/
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
.. __: https://psycopg.org/
Installation
------------
Building Psycopg requires a few prerequisites (a C compiler, some development
packages): please check the install_ and the faq_ documents in the ``doc`` dir
or online for the details.
If prerequisites are met, you can install psycopg like any other Python
package, using ``pip`` to download it from PyPI_::
$ pip install psycopg2
or using ``setup.py`` if you have downloaded the source package locally::
$ python setup.py build
$ sudo python setup.py install
You can also obtain a stand-alone package, not requiring a compiler or
external libraries, by installing the `psycopg2-binary`_ package from PyPI::
$ pip install psycopg2-binary
The binary package is a practical choice for development and testing but in
production it is advised to use the package built from sources.
.. _PyPI: https://pypi.org/project/psycopg2/
.. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/
.. _install: https://www.psycopg.org/docs/install.html#install-from-source
.. _faq: https://www.psycopg.org/docs/faq.html#faq-compile
:Build status: |gh-actions|
.. |gh-actions| image:: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml/badge.svg
:target: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml
:alt: Build status

8
doc/.gitignore vendored
View File

@ -1,8 +0,0 @@
env
src/_build/*
html/*
psycopg2.txt
src/sqlstate_errors.rst
# Added by psycopg-website to customize published docs
src/_templates/layout.html

676
doc/COPYING Normal file
View File

@ -0,0 +1,676 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -1,7 +1,7 @@
GNU LESSER GENERAL PUBLIC LICENSE GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.

1744
doc/ChangeLog-1.x Normal file

File diff suppressed because it is too large Load Diff

43
doc/HACKING Normal file
View File

@ -0,0 +1,43 @@
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,39 +1,23 @@
.PHONY: env help clean html package doctest .PHONY: help clean html text doctest
docs: html docs: html text
check: doctest check: doctest
# The environment is currently required to build the documentation. help:
# It is not clean by 'make clean' cd src && $(MAKE) $@
PYTHON := python$(PYTHON_VERSION) html:
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print("%d.%d" % sys.version_info[:2])') cd src && $(MAKE) $@
BUILD_DIR = $(shell pwd)/../build/lib.$(PYTHON_VERSION)
SPHINXBUILD ?= $$(pwd)/env/bin/sphinx-build
SPHOPTS = SPHINXBUILD=$(SPHINXBUILD)
html: package src/sqlstate_errors.rst
$(MAKE) $(SPHOPTS) -C src $@
cp -r src/_build/html . cp -r src/_build/html .
src/sqlstate_errors.rst: ../psycopg/sqlstate_errors.h $(BUILD_DIR) text:
./env/bin/python src/tools/make_sqlstate_docs.py $< > $@ cd src && $(MAKE) $@
cd src && tools/stitch_text.py index.rst _build/text > ../psycopg2.txt
$(BUILD_DIR):
$(MAKE) PYTHON=$(PYTHON) -C .. package
doctest: doctest:
$(MAKE) PYTHON=$(PYTHON) -C .. package cd src && $(MAKE) $@
$(MAKE) $(SPHOPTS) -C src $@
clean: clean:
$(MAKE) $(SPHOPTS) -C src $@ cd src && $(MAKE) $@
rm -rf html src/sqlstate_errors.rst rm -rf html psycopg2.txt
env: requirements.txt
$(PYTHON) -m venv env
./env/bin/pip install -r requirements.txt
echo "$$(pwd)/../build/lib.$(PYTHON_VERSION)" \
> env/lib/python$(PYTHON_VERSION)/site-packages/psycopg.pth

42
doc/README Normal file
View File

@ -0,0 +1,42 @@
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.

View File

@ -1,20 +0,0 @@
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: https://www.psycopg.org/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
You should find the rendered documentation in the ``html`` directory.

View File

@ -23,7 +23,7 @@ Date: 23 Oct 2001 09:53:11 +0600
We use psycopg and psycopg zope adapter since fisrt public We use psycopg and psycopg zope adapter since fisrt public
release (it seems version 0.4). Now it works on 3 our sites and in intranet release (it seems version 0.4). Now it works on 3 our sites and in intranet
applications. We had few problems, but all problems were quickly applications. We had few problems, but all problems were quckly
solved. The strong side of psycopg is that it's code is well organized solved. The strong side of psycopg is that it's code is well organized
and easy to understand. When I found a problem with non-ISO datestyle in first and easy to understand. When I found a problem with non-ISO datestyle in first
version of psycopg, it took for me 15 or 20 minutes to learn code and version of psycopg, it took for me 15 or 20 minutes to learn code and

View File

@ -255,7 +255,7 @@ Cursor Objects
display_size, internal_size, precision, scale, display_size, internal_size, precision, scale,
null_ok). The first two items (name and type_code) are null_ok). The first two items (name and type_code) are
mandatory, the other five are optional and must be set to mandatory, the other five are optional and must be set to
None if meaningful values are not provided. None if meaningfull values are not provided.
This attribute will be None for operations that This attribute will be None for operations that
do not return rows or if the cursor has not had an do not return rows or if the cursor has not had an

View File

@ -1,68 +0,0 @@
How to make a psycopg2 release
==============================
- Edit ``setup.py`` and set a stable version release. Use PEP 440 to choose
version numbers, e.g.
- ``2.7``: a new major release, new features
- ``2.7.1``: a bugfix release
- ``2.7.1.1``: a release to fix packaging problems
- ``2.7.2.dev0``: version held during development, non-public test packages...
- ``2.8b1``: a beta for public tests
In the rest of this document we assume you have exported the version number
into an environment variable, e.g.::
$ export VERSION=2.8.4
- Push psycopg2 to master or to the maint branch. Make sure tests on `GitHub
Actions`__.
.. __: https://github.com/psycopg/psycopg2/actions/workflows/tests.yml
- Create a signed tag with the content of the relevant NEWS bit and push it.
E.g.::
# Tag name will be 2_8_4
$ git tag -a -s ${VERSION//\./_}
Psycopg 2.8.4 released
What's new in psycopg 2.8.4
---------------------------
New features:
- Fixed bug blah (:ticket:`#42`).
...
- Create the packages:
- On GitHub Actions run manually a `package build workflow`__.
.. __: https://github.com/psycopg/psycopg2/actions/workflows/packages.yml
- When the workflows have finished download the packages from the job
artifacts.
- Only for stable packages: upload the signed packages on PyPI::
$ twine upload -s wheelhouse/psycopg2-${VERSION}/*
- Create a release and release notes in the psycopg website, announce to
psycopg and pgsql-announce mailing lists.
- Edit ``setup.py`` changing the version again (e.g. go to ``2.8.5.dev0``).
Releasing test packages
-----------------------
Test packages may be uploaded on the `PyPI testing site`__ using::
$ twine upload -s -r testpypi wheelhouse/psycopg2-${VERSION}/*
assuming `proper configuration`__ of ``~/.pypirc``.
.. __: https://test.pypi.org/project/psycopg2/
.. __: https://wiki.python.org/moin/TestPyPI

View File

@ -1,2 +0,0 @@
Sphinx
sphinx-better-theme

View File

@ -1,50 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile requirements.in
#
alabaster==0.7.13
# via sphinx
babel==2.12.1
# via sphinx
certifi>=2023.7.22
# via requests
charset-normalizer==3.1.0
# via requests
docutils==0.19
# via sphinx
idna==3.4
# via requests
imagesize==1.4.1
# via sphinx
jinja2==3.1.2
# via sphinx
markupsafe==2.1.2
# via jinja2
packaging==23.1
# via sphinx
pygments==2.15.0
# via sphinx
requests==2.31.0
# via sphinx
snowballstemmer==2.2.0
# via sphinx
sphinx==6.1.3
# via -r requirements.in
sphinx-better-theme==0.1.5
# via -r requirements.in
sphinxcontrib-applehelp==1.0.4
# via sphinx
sphinxcontrib-devhelp==1.0.2
# via sphinx
sphinxcontrib-htmlhelp==2.0.1
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
urllib3==1.26.17
# via requests

View File

@ -1,3 +1,5 @@
@import url("default.css");
blockquote { blockquote {
font-style: italic; font-style: italic;
} }
@ -12,18 +14,11 @@ div.dbapi-extension {
border: 1px solid #aaf; border: 1px solid #aaf;
} }
code.sql,
tt.sql { tt.sql {
font-size: 1em; font-size: 1em;
background-color: transparent; background-color: transparent;
} }
a > code.sql,
a > tt.sql {
font-weight: normal;
}
a > code.sql:hover,
a > tt.sql:hover { a > tt.sql:hover {
text-decoration: underline; text-decoration: underline;
} }
@ -35,102 +30,3 @@ dl.faq dt {
table.data-types div.line-block { table.data-types div.line-block {
margin-bottom: 0; margin-bottom: 0;
} }
/* better theme customisation */
body {
background-color: #216464;
}
header, .related, .document, footer {
background-color: white;
}
header h1 {
font-size: 150%;
margin-bottom: 0;
padding: 0.5rem 10px 0.5rem 10px;
}
h1, h2, h3 {
font-weight: normal;
}
.body h1, .body h2, .body h3 {
color: #074848;
}
h1 {
font-size: 200%;
}
h2 {
font-size: 160%;
}
h3 {
font-size: 140%;
}
footer#pagefooter {
margin-bottom: 1rem;
font-size: 85%;
color: #444;
}
#rellinks, #breadcrumbs {
padding-right: 10px;
padding-left: 10px;
}
.sphinxsidebar {
padding-left: 10px;
}
.bodywrapper {
padding-right: 10px;
}
div.body h1, div.body h2, div.body h3 {
background-color: #f2f2f2;
border-bottom: 1px solid #d0d0d0;
}
div.body p.rubric {
border-bottom: 1px solid #d0d0d0;
}
body .sphinxsidebar .search {
margin-top: 0;
}
html pre {
background-color: #efc;
border: 1px solid #ac9;
border-left: none;
border-right: none;
}
a, a:visited {
color: #0b6868;
}
th {
background-color: #ede;
}
code.xref, a code {
font-weight: bold;
}
code.descname {
font-weight: bold;
font-size: 120%;
}
@media (max-width: 820px) {
body {
background-color: white;
}
}

View File

@ -1,6 +0,0 @@
{# Add a title over the search box #}
{%- if pagename != "search" %}
<h3>Quick search</h3>
{%- include "!searchbox.html" %}
{%- endif %}

View File

@ -12,7 +12,7 @@ More advanced topics
conn.commit() conn.commit()
def wait(conn): def wait(conn):
while True: while 1:
state = conn.poll() state = conn.poll()
if state == psycopg2.extensions.POLL_OK: if state == psycopg2.extensions.POLL_OK:
break break
@ -47,7 +47,7 @@ it is the class where query building, execution and result type-casting into
Python variables happens. Python variables happens.
The `~psycopg2.extras` module contains several examples of :ref:`connection The `~psycopg2.extras` module contains several examples of :ref:`connection
and cursor subclasses <cursor-subclasses>`. and cursor sublcasses <cursor-subclasses>`.
.. note:: .. note::
@ -145,9 +145,7 @@ geometric type:
... self.y = y ... self.y = y
>>> def adapt_point(point): >>> def adapt_point(point):
... x = adapt(point.x).getquoted() ... return AsIs("'(%s, %s)'" % (adapt(point.x), adapt(point.y)))
... y = adapt(point.y).getquoted()
... return AsIs("'(%s, %s)'" % (x, y))
>>> register_adapter(Point, adapt_point) >>> register_adapter(Point, adapt_point)
@ -156,7 +154,7 @@ geometric type:
.. |point| replace:: :sql:`point` .. |point| replace:: :sql:`point`
.. _point: https://www.postgresql.org/docs/current/static/datatype-geometric.html#DATATYPE-GEOMETRIC .. _point: http://www.postgresql.org/docs/current/static/datatype-geometric.html#DATATYPE-GEOMETRIC
The above function call results in the SQL command:: The above function call results in the SQL command::
@ -226,7 +224,7 @@ read:
>>> cur.execute("SELECT '(10.2,20.3)'::point") >>> cur.execute("SELECT '(10.2,20.3)'::point")
>>> point = cur.fetchone()[0] >>> point = cur.fetchone()[0]
>>> print(type(point), point.x, point.y) >>> print type(point), point.x, point.y
<class 'Point'> 10.2 20.3 <class 'Point'> 10.2 20.3
A typecaster created by `!new_type()` can be also used with A typecaster created by `!new_type()` can be also used with
@ -259,9 +257,9 @@ documentation), you should keep the connection in `~connection.autocommit`
mode if you wish to receive or send notifications in a timely manner. mode if you wish to receive or send notifications in a timely manner.
.. |LISTEN| replace:: :sql:`LISTEN` .. |LISTEN| replace:: :sql:`LISTEN`
.. _LISTEN: https://www.postgresql.org/docs/current/static/sql-listen.html .. _LISTEN: http://www.postgresql.org/docs/current/static/sql-listen.html
.. |NOTIFY| replace:: :sql:`NOTIFY` .. |NOTIFY| replace:: :sql:`NOTIFY`
.. _NOTIFY: https://www.postgresql.org/docs/current/static/sql-notify.html .. _NOTIFY: http://www.postgresql.org/docs/current/static/sql-notify.html
Notifications are received after every query execution. If the user is Notifications are received after every query execution. If the user is
interested in receiving notifications but not in performing any query, the interested in receiving notifications but not in performing any query, the
@ -270,7 +268,7 @@ wasting resources.
A simple application could poll the connection from time to time to check if A simple application could poll the connection from time to time to check if
something new has arrived. A better strategy is to use some I/O completion something new has arrived. A better strategy is to use some I/O completion
function such as :py:func:`~select.select` to sleep until awakened by the kernel when there is function such as :py:func:`~select.select` to sleep until awaken from the kernel when there is
some data to read on the connection, thereby using no CPU unless there is some data to read on the connection, thereby using no CPU unless there is
something to read:: something to read::
@ -284,20 +282,18 @@ something to read::
curs = conn.cursor() curs = conn.cursor()
curs.execute("LISTEN test;") curs.execute("LISTEN test;")
print("Waiting for notifications on channel 'test'") print "Waiting for notifications on channel 'test'"
while True: while 1:
if select.select([conn],[],[],5) == ([],[],[]): if select.select([conn],[],[],5) == ([],[],[]):
print("Timeout") print "Timeout"
else: else:
conn.poll() conn.poll()
while conn.notifies: while conn.notifies:
notify = conn.notifies.pop(0) notify = conn.notifies.pop()
print("Got NOTIFY:", notify.pid, notify.channel, notify.payload) print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'` Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
in a separate :program:`psql` shell, the output may look similar to: in a separate :program:`psql` shell, the output may look similar to::
.. code-block:: none
Waiting for notifications on channel 'test' Waiting for notifications on channel 'test'
Timeout Timeout
@ -314,10 +310,6 @@ received from a previous version server will have the
Added `~psycopg2.extensions.Notify` object and handling notification Added `~psycopg2.extensions.Notify` object and handling notification
payload. 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:: .. index::
@ -328,7 +320,7 @@ received from a previous version server will have the
Asynchronous support Asynchronous support
-------------------- --------------------
.. versionadded:: 2.2 .. versionadded:: 2.2.0
Psycopg can issue asynchronous queries to a PostgreSQL database. An asynchronous Psycopg can issue asynchronous queries to a PostgreSQL database. An asynchronous
communication style is established passing the parameter *async*\=1 to the communication style is established passing the parameter *async*\=1 to the
@ -347,7 +339,7 @@ together with the Python :py:func:`~select.select` function in order to carry on
asynchronous operations with Psycopg:: asynchronous operations with Psycopg::
def wait(conn): def wait(conn):
while True: while 1:
state = conn.poll() state = conn.poll()
if state == psycopg2.extensions.POLL_OK: if state == psycopg2.extensions.POLL_OK:
break break
@ -375,7 +367,7 @@ completely non-blocking connection attempt: see the libpq documentation for
|PQconnectStart|_. |PQconnectStart|_.
.. |PQconnectStart| replace:: `!PQconnectStart()` .. |PQconnectStart| replace:: `!PQconnectStart()`
.. _PQconnectStart: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTSTARTPARAMS .. _PQconnectStart: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNECTSTARTPARAMS
The same loop should be also used to perform nonblocking queries: after The same loop should be also used to perform nonblocking queries: after
sending a query via `~cursor.execute()` or `~cursor.callproc()`, call sending a query via `~cursor.execute()` or `~cursor.callproc()`, call
@ -425,7 +417,7 @@ this will be probably implemented in a future release.
Support for coroutine libraries Support for coroutine libraries
------------------------------- -------------------------------
.. versionadded:: 2.2 .. versionadded:: 2.2.0
Psycopg can be used together with coroutine_\-based libraries and participate Psycopg can be used together with coroutine_\-based libraries and participate
in cooperative multithreading. in cooperative multithreading.
@ -468,7 +460,7 @@ example callback (using `!select()` to block) is provided as
`psycopg2.extras.wait_select()`: it boils down to something similar to:: `psycopg2.extras.wait_select()`: it boils down to something similar to::
def wait_select(conn): def wait_select(conn):
while True: while 1:
state = conn.poll() state = conn.poll()
if state == extensions.POLL_OK: if state == extensions.POLL_OK:
break break
@ -484,14 +476,14 @@ psycopg2 scope, as the callback can be tied to the libraries' implementation
details. You can check the `psycogreen`_ project for further informations and details. You can check the `psycogreen`_ project for further informations and
resources about the topic. resources about the topic.
.. _coroutine: https://en.wikipedia.org/wiki/Coroutine .. _coroutine: http://en.wikipedia.org/wiki/Coroutine
.. _greenlet: https://pypi.org/project/greenlet/ .. _greenlet: http://pypi.python.org/pypi/greenlet
.. _green threads: https://en.wikipedia.org/wiki/Green_threads .. _green threads: http://en.wikipedia.org/wiki/Green_threads
.. _Eventlet: https://eventlet.net/ .. _Eventlet: http://eventlet.net/
.. _gevent: http://www.gevent.org/ .. _gevent: http://www.gevent.org/
.. _SQLAlchemy: https://www.sqlalchemy.org/ .. _SQLAlchemy: http://www.sqlalchemy.org/
.. _psycogreen: https://github.com/psycopg/psycogreen/ .. _psycogreen: http://bitbucket.org/dvarrazzo/psycogreen/
.. __: https://www.postgresql.org/docs/current/static/libpq-async.html .. __: http://www.postgresql.org/docs/current/static/libpq-async.html
.. warning:: .. warning::
@ -511,89 +503,3 @@ resources about the topic.
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()
.. index::
single: Replication
.. _replication-support:
Replication protocol support
----------------------------
.. versionadded:: 2.7
Modern PostgreSQL servers (version 9.0 and above) support replication. The
replication protocol is built on top of the client-server protocol and can be
operated using ``libpq``, as such it can be also operated by ``psycopg2``.
The replication protocol can be operated on both synchronous and
:ref:`asynchronous <async-support>` connections.
Server version 9.4 adds a new feature called *Logical Replication*.
.. seealso::
- PostgreSQL `Streaming Replication Protocol`__
.. __: https://www.postgresql.org/docs/current/static/protocol-replication.html
Logical replication Quick-Start
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You must be using PostgreSQL server version 9.4 or above to run this quick
start.
Make sure that replication connections are permitted for user ``postgres`` in
``pg_hba.conf`` and reload the server configuration. You also need to set
``wal_level=logical`` and ``max_wal_senders``, ``max_replication_slots`` to
value greater than zero in ``postgresql.conf`` (these changes require a server
restart). Create a database ``psycopg2_test``.
Then run the following code to quickly try the replication support out. This
is not production code -- it's only intended as a simple demo of logical
replication::
from __future__ import print_function
import sys
import psycopg2
import psycopg2.extras
conn = psycopg2.connect('dbname=psycopg2_test user=postgres',
connection_factory=psycopg2.extras.LogicalReplicationConnection)
cur = conn.cursor()
try:
# test_decoding produces textual output
cur.start_replication(slot_name='pytest', decode=True)
except psycopg2.ProgrammingError:
cur.create_replication_slot('pytest', output_plugin='test_decoding')
cur.start_replication(slot_name='pytest', decode=True)
class DemoConsumer(object):
def __call__(self, msg):
print(msg.payload)
msg.cursor.send_feedback(flush_lsn=msg.data_start)
democonsumer = DemoConsumer()
print("Starting streaming, press Control-C to end...", file=sys.stderr)
try:
cur.consume_stream(democonsumer)
except KeyboardInterrupt:
cur.close()
conn.close()
print("The slot 'pytest' still exists. Drop it with "
"SELECT pg_drop_replication_slot('pytest'); if no longer needed.",
file=sys.stderr)
print("WARNING: Transaction logs will accumulate in pg_xlog "
"until the slot is dropped.", file=sys.stderr)
You can now make changes to the ``psycopg2_test`` database using a normal
psycopg2 session, ``psql``, etc. and see the logical decoding stream printed
by this demo client.
This will continue running until terminated with ``Control-C``.
For the details see :ref:`replication-objects`.

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# #
# Psycopg documentation build configuration file, created by # Psycopg documentation build configuration file, created by
# sphinx-quickstart on Sun Feb 7 13:48:41 2010. # sphinx-quickstart on Sun Feb 7 13:48:41 2010.
@ -10,9 +11,7 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import os import sys, os
import sys
from better import better_theme_path
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@ -23,16 +22,11 @@ sys.path.append(os.path.abspath('tools/lib'))
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.ifconfig',
'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx' ]
'sphinx.ext.todo',
'sphinx.ext.ifconfig',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
]
# Specific extensions for Psycopg documentation. # Specific extensions for Psycopg documentation.
extensions += ['dbapi_extension', 'sql_role', 'ticket_role'] extensions += [ 'dbapi_extension', 'sql_role', 'ticket_role' ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
@ -41,16 +35,14 @@ templates_path = ['_templates']
source_suffix = '.rst' source_suffix = '.rst'
# The encoding of source files. # The encoding of source files.
# source_encoding = 'utf-8' #source_encoding = 'utf-8'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = 'Psycopg' project = u'Psycopg'
copyright = ( copyright = u'2001-2013, Federico Di Gregorio. Documentation by Daniele Varrazzo'
'2001-2021, Federico Di Gregorio, Daniele Varrazzo, The Psycopg Team'
)
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@ -62,32 +54,32 @@ version = '2.0'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
try: try:
import psycopg2 import psycopg2
except ImportError:
print("WARNING: couldn't import psycopg to read version.")
release = version
else:
release = psycopg2.__version__.split()[0] release = psycopg2.__version__.split()[0]
version = '.'.join(release.split('.')[:2]) version = '.'.join(release.split('.')[:2])
except ImportError:
print "WARNING: couldn't import psycopg to read version."
release = version
intersphinx_mapping = {'py': ('https://docs.python.org/3', None)} intersphinx_mapping = {
'py': ('http://docs.python.org/', None),
'py3': ('http://docs.python.org/3.2', None),
}
# Pattern to generate links to the bug tracker # Pattern to generate links to the bug tracker
ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s' ticket_url = 'http://psycopg.lighthouseapp.com/projects/62710/tickets/%s'
ticket_remap_until = 25
ticket_remap_offset = 230
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
# language = None #language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
# today = '' #today = ''
# Else, today_fmt is used as the format for a strftime call. # Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y' #today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build. # List of documents that shouldn't be included in the build.
# unused_docs = [] #unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched # List of directories, relative to source directory, that shouldn't be searched
# for source files. # for source files.
@ -97,25 +89,21 @@ exclude_trees = ['_build', 'html']
default_role = 'obj' default_role = 'obj'
# If true, '()' will be appended to :func: etc. cross-reference text. # If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True #add_function_parentheses = True
# If true, the current module name will be prepended to all description # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
# add_module_names = True #add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
# show_authors = False #show_authors = False
# Using 'python' instead of the default gives warnings if parsing an example
# fails, instead of defaulting to none
highlight_language = 'python'
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
# modindex_common_prefix = [] #modindex_common_prefix = []
# Include TODO items in the documentation # Include TODO items in the documentation
todo_include_todos = False todo_include_todos = False
@ -123,10 +111,12 @@ todo_include_todos = False
rst_epilog = """ rst_epilog = """
.. |DBAPI| replace:: DB API 2.0 .. |DBAPI| replace:: DB API 2.0
.. _DBAPI: https://www.python.org/dev/peps/pep-0249/ .. _DBAPI: http://www.python.org/dev/peps/pep-0249/
.. _transaction isolation level: .. _transaction isolation level:
https://www.postgresql.org/docs/current/static/transaction-iso.html http://www.postgresql.org/docs/current/static/transaction-iso.html
.. _mx.DateTime: http://www.egenix.com/products/python/mxBase/mxDateTime/
.. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)` .. |MVCC| replace:: :abbr:`MVCC (Multiversion concurrency control)`
""" """
@ -135,41 +125,35 @@ rst_epilog = """
# The theme to use for HTML and HTML Help pages. Major themes that come with # The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'. # Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'better' html_theme = 'default'
# The stylesheet to use with HTML output: this will include the original one # The stylesheet to use with HTML output: this will include the original one
# adding a few classes. # adding a few classes.
# html_style = 'psycopg.css' html_style = 'psycopg.css'
# Hide the sphinx footer
html_show_sphinx = False
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
html_theme_options = { #html_theme_options = {}
'linktotheme': False,
'cssfiles': ['_static/psycopg.css'],
}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [better_theme_path] #html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
# html_title = None #html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # A shorter title for the navigation bar. Default is the same as html_title.
html_short_title = 'Home' #html_short_title = None
# The name of an image file (relative to this directory) to place at the top # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # of the sidebar.
# html_logo = None #html_logo = None
# The name of an image file (within the static path) to use as favicon of the # The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
# html_favicon = None #html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
@ -178,41 +162,38 @@ html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y' #html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
# html_use_smartypants = True #html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
# no need for the prev/next topic link using better theme: they are on top #html_sidebars = {}
html_sidebars = {
'**': ['localtoc.html', 'searchbox.html'],
}
# Additional templates that should be rendered to pages, maps page names to # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
# html_additional_pages = {} #html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
# html_use_modindex = True #html_use_modindex = True
# If false, no index is generated. # If false, no index is generated.
# html_use_index = True #html_use_index = True
# If true, the index is split into individual pages for each letter. # If true, the index is split into individual pages for each letter.
# html_split_index = False #html_split_index = False
# If true, links to the reST sources are added to the pages. # If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True #html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will # If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # base URL from which the finished HTML is served.
# html_use_opensearch = '' #html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = '' #html_file_suffix = ''
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'psycopgdoc' htmlhelp_basename = 'psycopgdoc'
@ -221,41 +202,35 @@ htmlhelp_basename = 'psycopgdoc'
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4'). # The paper size ('letter' or 'a4').
# latex_paper_size = 'letter' #latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
# latex_font_size = '10pt' #latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
( ('index', 'psycopg.tex', u'Psycopg Documentation',
'index', u'Federico Di Gregorio', 'manual'),
'psycopg.tex',
'Psycopg Documentation',
'Federico Di Gregorio',
'manual',
)
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
# latex_logo = None #latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
# latex_use_parts = False #latex_use_parts = False
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
# latex_preamble = '' #latex_preamble = ''
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
# latex_appendices = [] #latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
# latex_use_modindex = True #latex_use_modindex = True
toc_object_entries = False
doctest_global_setup = """ doctest_global_setup = """

View File

@ -21,28 +21,6 @@ The ``connection`` class
Connections are thread safe and can be shared among many threads. See Connections are thread safe and can be shared among many threads. See
:ref:`thread-safety` for details. :ref:`thread-safety` for details.
Connections can be used as context managers. Note that a context wraps a
transaction: if the context exits with success the transaction is
committed, if it exits with an exception the transaction is rolled back.
Note that the connection is not closed by the context and it can be used
for several contexts.
.. code:: python
conn = psycopg2.connect(DSN)
with conn:
with conn.cursor() as curs:
curs.execute(SQL1)
with conn:
with conn.cursor() as curs:
curs.execute(SQL2)
# leaving contexts doesn't close the connection
conn.close()
.. method:: cursor(name=None, cursor_factory=None, scrollable=None, withhold=False) .. method:: cursor(name=None, cursor_factory=None, scrollable=None, withhold=False)
Return a new `cursor` object using the connection. Return a new `cursor` object using the connection.
@ -63,6 +41,11 @@ The ``connection`` class
previously only valid PostgreSQL identifiers were accepted as previously only valid PostgreSQL identifiers were accepted as
cursor name. cursor name.
.. warning::
It is unsafe to expose the *name* to an untrusted source, for
instance you shouldn't allow *name* to be read from a HTML form.
Consider it as part of the query, not as a query parameter.
The *cursor_factory* argument can be used to create non-standard The *cursor_factory* argument can be used to create non-standard
cursors. The class returned must be a subclass of cursors. The class returned must be a subclass of
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for `psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
@ -139,7 +122,7 @@ The ``connection`` class
with a `~connection.commit()`/`~connection.rollback()` before with a `~connection.commit()`/`~connection.rollback()` before
closing. closing.
.. _PgBouncer: http://www.pgbouncer.org/ .. _PgBouncer: http://pgbouncer.projects.postgresql.org/
.. index:: .. index::
@ -220,7 +203,7 @@ The ``connection`` class
.. seealso:: the |PREPARE TRANSACTION|_ PostgreSQL command. .. seealso:: the |PREPARE TRANSACTION|_ PostgreSQL command.
.. |PREPARE TRANSACTION| replace:: :sql:`PREPARE TRANSACTION` .. |PREPARE TRANSACTION| replace:: :sql:`PREPARE TRANSACTION`
.. _PREPARE TRANSACTION: https://www.postgresql.org/docs/current/static/sql-prepare-transaction.html .. _PREPARE TRANSACTION: http://www.postgresql.org/docs/current/static/sql-prepare-transaction.html
.. index:: .. index::
@ -246,7 +229,7 @@ The ``connection`` class
.. seealso:: the |COMMIT PREPARED|_ PostgreSQL command. .. seealso:: the |COMMIT PREPARED|_ PostgreSQL command.
.. |COMMIT PREPARED| replace:: :sql:`COMMIT PREPARED` .. |COMMIT PREPARED| replace:: :sql:`COMMIT PREPARED`
.. _COMMIT PREPARED: https://www.postgresql.org/docs/current/static/sql-commit-prepared.html .. _COMMIT PREPARED: http://www.postgresql.org/docs/current/static/sql-commit-prepared.html
.. index:: .. index::
@ -268,7 +251,7 @@ The ``connection`` class
.. seealso:: the |ROLLBACK PREPARED|_ PostgreSQL command. .. seealso:: the |ROLLBACK PREPARED|_ PostgreSQL command.
.. |ROLLBACK PREPARED| replace:: :sql:`ROLLBACK PREPARED` .. |ROLLBACK PREPARED| replace:: :sql:`ROLLBACK PREPARED`
.. _ROLLBACK PREPARED: https://www.postgresql.org/docs/current/static/sql-rollback-prepared.html .. _ROLLBACK PREPARED: http://www.postgresql.org/docs/current/static/sql-rollback-prepared.html
.. index:: .. index::
@ -289,7 +272,7 @@ The ``connection`` class
transactions initiated by a program using such driver should be transactions initiated by a program using such driver should be
unpacked correctly. unpacked correctly.
.. __: https://jdbc.postgresql.org/ .. __: http://jdbc.postgresql.org/
Xids returned by `!tpc_recover()` also have extra attributes Xids returned by `!tpc_recover()` also have extra attributes
`~psycopg2.extensions.Xid.prepared`, `~psycopg2.extensions.Xid.owner`, `~psycopg2.extensions.Xid.prepared`, `~psycopg2.extensions.Xid.owner`,
@ -299,7 +282,7 @@ The ``connection`` class
.. seealso:: the |pg_prepared_xacts|_ system view. .. seealso:: the |pg_prepared_xacts|_ system view.
.. |pg_prepared_xacts| replace:: `pg_prepared_xacts` .. |pg_prepared_xacts| replace:: `pg_prepared_xacts`
.. _pg_prepared_xacts: https://www.postgresql.org/docs/current/static/view-pg-prepared-xacts.html .. _pg_prepared_xacts: http://www.postgresql.org/docs/current/static/view-pg-prepared-xacts.html
@ -312,8 +295,8 @@ The ``connection`` class
.. attribute:: closed .. attribute:: closed
Read-only integer attribute: 0 if the connection is open, nonzero if Read-only attribute reporting whether the database connection is open
it is closed or broken. (0) or closed (1).
.. method:: cancel .. method:: cancel
@ -331,7 +314,7 @@ The ``connection`` class
|PQcancel|_. |PQcancel|_.
.. |PQcancel| replace:: `!PQcancel()` .. |PQcancel| replace:: `!PQcancel()`
.. _PQcancel: https://www.postgresql.org/docs/current/static/libpq-cancel.html#LIBPQ-PQCANCEL .. _PQcancel: http://www.postgresql.org/docs/current/static/libpq-cancel.html#LIBPQ-PQCANCEL
.. versionadded:: 2.3 .. versionadded:: 2.3
@ -347,10 +330,10 @@ The ``connection`` class
available for recover. available for recover.
.. |RESET| replace:: :sql:`RESET` .. |RESET| replace:: :sql:`RESET`
.. _RESET: https://www.postgresql.org/docs/current/static/sql-reset.html .. _RESET: http://www.postgresql.org/docs/current/static/sql-reset.html
.. |SET SESSION AUTHORIZATION| replace:: :sql:`SET SESSION AUTHORIZATION` .. |SET SESSION AUTHORIZATION| replace:: :sql:`SET SESSION AUTHORIZATION`
.. __: https://www.postgresql.org/docs/current/static/sql-set-session-authorization.html .. __: http://www.postgresql.org/docs/current/static/sql-set-session-authorization.html
.. versionadded:: 2.0.12 .. versionadded:: 2.0.12
@ -360,28 +343,25 @@ The ``connection`` class
Read-only string containing the connection string used by the Read-only string containing the connection string used by the
connection. connection.
If a password was specified in the connection string it will be
obscured.
.. rubric:: Transaction control methods and attributes.
.. index:: .. index::
pair: Transaction; Autocommit pair: Transaction; Autocommit
pair: Transaction; Isolation level pair: Transaction; Isolation level
.. method:: set_session(isolation_level=None, readonly=None, deferrable=None, autocommit=None) .. method:: set_session([isolation_level,] [readonly,] [deferrable,] [autocommit])
Set one or more parameters for the next transactions or statements in Set one or more parameters for the next transactions or statements in
the current session. 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
:param isolation_level: set the `isolation level`_ for the next :param isolation_level: set the `isolation level`_ for the next
transactions/statements. The value can be one of the literal transactions/statements. The value can be one of the
values ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE :ref:`constants <isolation-level-constants>` defined in the
READ``, ``SERIALIZABLE`` or the equivalent :ref:`constant `~psycopg2.extensions` module or one of the literal values
<isolation-level-constants>` defined in the `~psycopg2.extensions` ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
module. ``SERIALIZABLE``.
:param readonly: if `!True`, set the connection to read only; :param readonly: if `!True`, set the connection to read only;
read/write if `!False`. read/write if `!False`.
:param deferrable: if `!True`, set the connection to deferrable; :param deferrable: if `!True`, set the connection to deferrable;
@ -390,59 +370,35 @@ The ``connection`` class
PostgreSQL session setting but an alias for setting the PostgreSQL session setting but an alias for setting the
`autocommit` attribute. `autocommit` attribute.
.. _isolation level:
https://www.postgresql.org/docs/current/static/transaction-iso.html
Arguments set to `!None` (the default for all) will not be changed.
The parameters *isolation_level*, *readonly* and *deferrable* also The parameters *isolation_level*, *readonly* and *deferrable* also
accept the string ``DEFAULT`` as a value: the effect is to reset the accept the string ``DEFAULT`` as a value: the effect is to reset the
parameter to the server default. Defaults are defined by the server parameter to the server default.
configuration: see values for |default_transaction_isolation|__,
.. _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|__,
|default_transaction_read_only|__, |default_transaction_deferrable|__. |default_transaction_read_only|__, |default_transaction_deferrable|__.
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation` .. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-ISOLATION
.. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only` .. |default_transaction_read_only| replace:: :sql:`default_transaction_read_only`
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-READ-ONLY
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable` .. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-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::
.. seealso:: |SET TRANSACTION|_ for further details about the behaviour There is currently no builtin method to read the current value for
of the transaction parameters in the server. the parameters: use :sql:`SHOW default_transaction_...` to read
the values from the backend.
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
.. _SET TRANSACTION: https://www.postgresql.org/docs/current/static/sql-set-transaction.html
.. versionadded:: 2.4.2 .. versionadded:: 2.4.2
.. versionchanged:: 2.7
Before this version, the function would have set
:sql:`default_transaction_*` attribute in the current session;
this implementation has the problem of not playing well with
external connection pooling working at transaction level and not
resetting the state of the session: changing the default
transaction would pollute the connections in the pool and create
problems to other applications using the same pool.
Starting from 2.7, if the connection is not autocommit, the
transaction characteristics are issued together with :sql:`BEGIN`
and will leave the :sql:`default_transaction_*` settings untouched.
For example::
conn.set_session(readonly=True)
will not change :sql:`default_transaction_read_only`, but
following transaction will start with a :sql:`BEGIN READ ONLY`.
Conversely, using::
conn.set_session(readonly=True, autocommit=True)
will set :sql:`default_transaction_read_only` to :sql:`on` and
rely on the server to apply the read only state to whatever
transaction, implicit or explicit, is executed in the connection.
.. attribute:: autocommit .. attribute:: autocommit
@ -462,8 +418,8 @@ The ``connection`` class
By default, any query execution, including a simple :sql:`SELECT` By default, any query execution, including a simple :sql:`SELECT`
will start a transaction: for long-running programs, if no further will start a transaction: for long-running programs, if no further
action is taken, the session will remain "idle in transaction", an action is taken, the session will remain "idle in transaction", a
undesirable condition for several reasons (locks are held by condition non desiderable for several reasons (locks are held by
the session, tables bloat...). For long lived scripts, either the session, tables bloat...). For long lived scripts, either
ensure to terminate a transaction as soon as possible or use an ensure to terminate a transaction as soon as possible or use an
autocommit connection. autocommit connection.
@ -472,83 +428,31 @@ The ``connection`` class
.. attribute:: isolation_level .. attribute:: isolation_level
Return or set the `transaction isolation level`_ for the current
session. The value is one of the :ref:`isolation-level-constants`
defined in the `psycopg2.extensions` module. On set it is also
possible to use one of the literal values ``READ UNCOMMITTED``, ``READ
COMMITTED``, ``REPEATABLE READ``, ``SERIALIZABLE``, ``DEFAULT``.
.. versionchanged:: 2.7
the property is writable.
.. versionchanged:: 2.7
the default value for `!isolation_level` is
`~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`; previously the
property would have queried the server and returned the real value
applied. To know this value you can run a query such as :sql:`show
transaction_isolation`. Usually the default value is `READ
COMMITTED`, but this may be changed in the server configuration.
This value is now entirely separate from the `autocommit`
property: in previous version, if `!autocommit` was set to `!True`
this property would have returned
`~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT`; it will now
return the server isolation level.
.. attribute:: readonly
Return or set the read-only status for the current session. Available
values are `!True` (new transactions will be in read-only mode),
`!False` (new transactions will be writable), `!None` (use the default
configured for the server by :sql:`default_transaction_read_only`).
.. versionadded:: 2.7
.. attribute:: deferrable
Return or set the `deferrable status`__ for the current session.
Available values are `!True` (new transactions will be in deferrable
mode), `!False` (new transactions will be in non deferrable mode),
`!None` (use the default configured for the server by
:sql:`default_transaction_deferrable`).
.. __: `SET TRANSACTION`_
.. versionadded:: 2.7
.. method:: set_isolation_level(level) .. method:: set_isolation_level(level)
.. note:: .. note::
This is a legacy method mixing `~conn.isolation_level` and From version 2.4.2, `set_session()` and `autocommit`, offer
`~conn.autocommit`. Using the respective properties is a better finer control on the transaction characteristics.
option.
Set the `transaction isolation level`_ for the current session. Read or set the `transaction isolation level`_ for the current session.
The level defines the different phenomena that can happen in the The level defines the different phenomena that can happen in the
database between concurrent transactions. database between concurrent transactions.
The value set is an integer: symbolic constants are defined in The value set or read is an integer: symbolic constants are defined in
the module `psycopg2.extensions`: see the module `psycopg2.extensions`: see
:ref:`isolation-level-constants` for the available values. :ref:`isolation-level-constants` for the available values.
The default level is `~psycopg2.extensions.ISOLATION_LEVEL_DEFAULT`: The default level is :sql:`READ COMMITTED`: at this level a
at this level a transaction is automatically started the first time a transaction is automatically started the first time a database command
database command is executed. If you want an *autocommit* mode, is executed. If you want an *autocommit* mode, switch to
switch to `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before `~psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT` before
executing any command:: executing any command::
>>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) >>> conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
See also :ref:`transactions-control`. See also :ref:`transactions-control`.
.. index:: .. index::
pair: Client; Encoding pair: Client; Encoding
@ -559,7 +463,7 @@ The ``connection`` class
is the encoding defined by the database. It should be one of the is the encoding defined by the database. It should be one of the
`characters set supported by PostgreSQL`__ `characters set supported by PostgreSQL`__
.. __: https://www.postgresql.org/docs/current/static/multibyte.html .. __: http://www.postgresql.org/docs/current/static/multibyte.html
.. index:: .. index::
@ -571,29 +475,21 @@ The ``connection`` class
the session. the session.
.. doctest:: .. doctest::
:options: +NORMALIZE_WHITESPACE :options: NORMALIZE_WHITESPACE
>>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);") >>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);")
>>> pprint(conn.notices) >>> pprint(conn.notices)
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n', ['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'] '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 To avoid a leak in case excessive notices are generated, only the last
50 messages are kept. This check is only in place if the `!notices` 50 messages are kept.
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 You can configure what messages to receive using `PostgreSQL logging
configuration parameters`__ such as ``log_statement``, configuration parameters`__ such as ``log_statement``,
``client_min_messages``, ``log_min_duration_statement`` etc. ``client_min_messages``, ``log_min_duration_statement`` etc.
.. __: https://www.postgresql.org/docs/current/static/runtime-config-logging.html .. __: http://www.postgresql.org/docs/current/static/runtime-config-logging.html
.. attribute:: notifies .. attribute:: notifies
@ -609,12 +505,6 @@ The ``connection`` class
the payload was not accessible. To keep backward compatibility, the payload was not accessible. To keep backward compatibility,
`!Notify` objects can still be accessed as 2 items tuples. `!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 .. attribute:: cursor_factory
@ -625,14 +515,92 @@ The ``connection`` class
.. index:: .. index::
pair: Connection; Info pair: Backend; PID
.. attribute:: info .. method:: get_backend_pid()
A `~psycopg2.extensions.ConnectionInfo` object exposing information Returns the process ID (PID) of the backend server process handling
about the native libpq connection. this connection.
.. versionadded:: 2.8 Note that the PID belongs to a process executing on the database
server host, not the local host!
.. seealso:: libpq docs for `PQbackendPID()`__ for details.
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID
.. versionadded:: 2.0.8
.. index::
pair: Server; Parameters
.. method:: get_parameter_status(parameter)
Look up a current parameter setting of the server.
Potential values for ``parameter`` are: ``server_version``,
``server_encoding``, ``client_encoding``, ``is_superuser``,
``session_authorization``, ``DateStyle``, ``TimeZone``,
``integer_datetimes``, and ``standard_conforming_strings``.
If server did not report requested parameter, return `!None`.
.. seealso:: libpq docs for `PQparameterStatus()`__ for details.
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS
.. versionadded:: 2.0.12
.. index::
pair: Transaction; Status
.. method:: get_transaction_status()
Return the current session transaction status as an integer. Symbolic
constants for the values are defined in the module
`psycopg2.extensions`: see :ref:`transaction-status-constants`
for the available values.
.. seealso:: libpq docs for `PQtransactionStatus()`__ for details.
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
.. index::
pair: Protocol; Version
.. attribute:: protocol_version
A read-only integer representing frontend/backend protocol being used.
Currently Psycopg supports only protocol 3, which allows connection
to PostgreSQL server from version 7.4. Psycopg versions previous than
2.3 support both protocols 2 and 3.
.. seealso:: libpq docs for `PQprotocolVersion()`__ for details.
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION
.. versionadded:: 2.0.12
.. index::
pair: Server; Version
.. attribute:: server_version
A read-only integer representing the backend version.
The number is formed by converting the major, minor, and revision
numbers into two-decimal-digit numbers and appending them together.
For example, version 8.1.5 will be returned as ``80105``.
.. seealso:: libpq docs for `PQserverVersion()`__ for details.
.. __: http://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION
.. versionadded:: 2.0.12
.. index:: .. index::
@ -645,8 +613,6 @@ The ``connection`` class
`psycopg2.extensions`: see :ref:`connection-status-constants` `psycopg2.extensions`: see :ref:`connection-status-constants`
for the available values. for the available values.
The status is undefined for `closed` connections.
.. method:: lobject([oid [, mode [, new_oid [, new_file [, lobject_factory]]]]]) .. method:: lobject([oid [, mode [, new_oid [, new_file [, lobject_factory]]]]])
@ -661,13 +627,13 @@ The ``connection`` class
:param new_oid: Create a new object using the specified OID. The :param new_oid: Create a new object using the specified OID. The
function raises `~psycopg2.OperationalError` if the OID is already function raises `~psycopg2.OperationalError` if the OID is already
in use. Default is 0, meaning assign a new one automatically. in use. Default is 0, meaning assign a new one automatically.
:param new_file: The name of a file to be imported in the database :param new_file: The name of a file to be imported in the the database
(using the |lo_import|_ function) (using the |lo_import|_ function)
:param lobject_factory: Subclass of :param lobject_factory: Subclass of
`~psycopg2.extensions.lobject` to be instantiated. `~psycopg2.extensions.lobject` to be instantiated.
.. |lo_import| replace:: `!lo_import()` .. |lo_import| replace:: `!lo_import()`
.. _lo_import: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT .. _lo_import: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
Available values for *mode* are: Available values for *mode* are:
@ -692,21 +658,17 @@ The ``connection`` class
support. support.
.. rubric:: Methods related to asynchronous support .. rubric:: Methods related to asynchronous support.
.. versionadded:: 2.2 .. versionadded:: 2.2.0
.. seealso:: :ref:`async-support` and :ref:`green-support`. .. seealso:: :ref:`async-support` and :ref:`green-support`.
.. attribute:: async .. attribute:: async
async_
Read only attribute: 1 if the connection is asynchronous, 0 otherwise. Read only attribute: 1 if the connection is asynchronous, 0 otherwise.
.. versionchanged:: 2.7 added the `!async_` alias for Python versions
where `!async` is a keyword.
.. method:: poll() .. method:: poll()
@ -716,7 +678,7 @@ The ``connection`` class
Return one of the constants defined in :ref:`poll-constants`. If it Return one of the constants defined in :ref:`poll-constants`. If it
returns `~psycopg2.extensions.POLL_OK` then the connection has been returns `~psycopg2.extensions.POLL_OK` then the connection has been
established or the query results are available on the client. estabilished or the query results are available on the client.
Otherwise wait until the file descriptor returned by `fileno()` is Otherwise wait until the file descriptor returned by `fileno()` is
ready to read or to write, as explained in :ref:`async-support`. ready to read or to write, as explained in :ref:`async-support`.
`poll()` should be also used by the function installed by `poll()` should be also used by the function installed by
@ -738,178 +700,6 @@ The ``connection`` class
Return `!True` if the connection is executing an asynchronous operation. Return `!True` if the connection is executing an asynchronous operation.
.. rubric:: Interoperation with other C API modules
.. attribute:: pgconn_ptr
Return the internal `!PGconn*` as integer. Useful to pass the libpq
raw connection structure to C functions, e.g. via `ctypes`::
>>> import ctypes
>>> import ctypes.util
>>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
>>> libpq.PQserverVersion.argtypes = [ctypes.c_void_p]
>>> libpq.PQserverVersion.restype = ctypes.c_int
>>> libpq.PQserverVersion(conn.pgconn_ptr)
90611
.. versionadded:: 2.8
.. method:: get_native_connection()
Return the internal `!PGconn*` wrapped in a PyCapsule object. This is
only useful for passing the `libpq` raw connection associated to this
connection object to other C-level modules that may have a use for it.
.. seealso:: Python C API `Capsules`__ docs.
.. __: https://docs.python.org/3.1/c-api/capsule.html
.. versionadded:: 2.8
.. rubric:: informative methods of the native connection
.. note::
These methods are better accessed using the `~connection.info`
attributes and may be dropped in future versions.
.. index::
pair: Transaction; Status
.. method:: get_transaction_status()
Also available as `~connection.info`\ `!.`\
`~psycopg2.extensions.ConnectionInfo.transaction_status`.
Return the current session transaction status as an integer. Symbolic
constants for the values are defined in the module
`psycopg2.extensions`: see :ref:`transaction-status-constants`
for the available values.
.. seealso:: libpq docs for `PQtransactionStatus()`__ for details.
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS
.. index::
pair: Protocol; Version
.. attribute:: protocol_version
Also available as `~connection.info`\ `!.`\
`~psycopg2.extensions.ConnectionInfo.protocol_version`.
A read-only integer representing frontend/backend protocol being used.
Currently Psycopg supports only protocol 3, which allows connection
to PostgreSQL server from version 7.4. Psycopg versions previous than
2.3 support both protocols 2 and 3.
.. seealso:: libpq docs for `PQprotocolVersion()`__ for details.
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION
.. versionadded:: 2.0.12
.. index::
pair: Server; Version
.. attribute:: server_version
Also available as `~connection.info`\ `!.`\
`~psycopg2.extensions.ConnectionInfo.server_version`.
A read-only integer representing the backend version.
The number is formed by converting the major, minor, and revision
numbers into two-decimal-digit numbers and appending them together.
For example, version 8.1.5 will be returned as ``80105``.
.. seealso:: libpq docs for `PQserverVersion()`__ for details.
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION
.. versionadded:: 2.0.12
.. index::
pair: Backend; PID
.. method:: get_backend_pid()
Also available as `~connection.info`\ `!.`\
`~psycopg2.extensions.ConnectionInfo.backend_pid`.
Returns the process ID (PID) of the backend server process *you
connected to*. Note that if you use a connection pool service such as
PgBouncer_ this value will not be updated if your connection is
switched to a different backend.
Note that the PID belongs to a process executing on the database
server host, not the local host!
.. seealso:: libpq docs for `PQbackendPID()`__ for details.
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID
.. versionadded:: 2.0.8
.. index::
pair: Server; Parameters
.. method:: get_parameter_status(parameter)
Also available as `~connection.info`\ `!.`\
`~psycopg2.extensions.ConnectionInfo.parameter_status()`.
Look up a current parameter setting of the server.
Potential values for ``parameter`` are: ``server_version``,
``server_encoding``, ``client_encoding``, ``is_superuser``,
``session_authorization``, ``DateStyle``, ``TimeZone``,
``integer_datetimes``, and ``standard_conforming_strings``.
If server did not report requested parameter, return `!None`.
.. seealso:: libpq docs for `PQparameterStatus()`__ for details.
.. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS
.. versionadded:: 2.0.12
.. index::
pair: Connection; Parameters
.. method:: get_dsn_parameters()
Also available as `~connection.info`\ `!.`\
`~psycopg2.extensions.ConnectionInfo.dsn_parameters`.
Get the effective dsn parameters for the connection as a dictionary.
The *password* parameter is removed from the result.
Example::
>>> conn.get_dsn_parameters()
{'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'}
Requires libpq >= 9.3.
.. seealso:: libpq docs for `PQconninfo()`__ for details.
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFO
.. versionadded:: 2.7
.. testcode:: .. testcode::
:hide: :hide:

View File

@ -34,62 +34,49 @@ The ``cursor`` class
many cursors from the same connection and should use each cursor from many cursors from the same connection and should use each cursor from
a single thread. See :ref:`thread-safety` for details. a single thread. See :ref:`thread-safety` for details.
Cursors can be used as context managers: leaving the context will close
the cursor.
.. code:: python
with conn.cursor() as curs:
curs.execute(SQL)
# the cursor is now closed
.. attribute:: description .. attribute:: description
Read-only attribute describing the result of a query. It is a This read-only attribute is a sequence of 7-item sequences.
sequence of `~psycopg2.extensions.Column` instances, each one
describing one result column in order. The attribute is `!None` for
operations that do not return rows or if the cursor has not had an
operation invoked via the |execute*|_ methods yet.
For compatibility with the DB-API, every object can be unpacked as a Each of these sequences is a named tuple (a regular tuple if
7-items sequence: the attributes retuned this way are the following. :func:`collections.namedtuple` is not available) containing information
For further details and other attributes available check the describing one result column:
`~psycopg2.extensions.Column` documentation.
0. `~psycopg2.extensions.Column.name`: the name of the column returned. 0. `!name`: the name of the column returned.
1. `!type_code`: the PostgreSQL OID of the column. You can use the
|pg_type|_ system table to get more informations about the type.
This is the value used by Psycopg to decide what Python type use
to represent the value. See also
:ref:`type-casting-from-sql-to-python`.
2. `!display_size`: the actual length of the column in bytes.
Obtaining this value is computationally intensive, so it is
always `!None` unless the :envvar:`PSYCOPG_DISPLAY_SIZE` parameter
is set at compile time. See also PQgetlength_.
3. `!internal_size`: the size in bytes of the column associated to
this column on the server. Set to a negative value for
variable-size types See also PQfsize_.
4. `!precision`: total number of significant digits in columns of
type |NUMERIC|_. `!None` for other types.
5. `!scale`: count of decimal digits in the fractional part in
columns of type |NUMERIC|. `!None` for other types.
6. `!null_ok`: always `!None` as not easy to retrieve from the libpq.
1. `~psycopg2.extensions.Column.type_code`: the PostgreSQL OID of the This attribute will be `!None` for operations that do not return rows
column. or if the cursor has not had an operation invoked via the
|execute*|_ methods yet.
2. `~psycopg2.extensions.Column.display_size`: the actual length of .. |pg_type| replace:: :sql:`pg_type`
the column in bytes. .. _pg_type: http://www.postgresql.org/docs/current/static/catalog-pg-type.html
.. _PQgetlength: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH
3. `~psycopg2.extensions.Column.internal_size`: the size in bytes of .. _PQfsize: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE
the column associated to this column on the server. .. _NUMERIC: http://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
.. |NUMERIC| replace:: :sql:`NUMERIC`
4. `~psycopg2.extensions.Column.precision`: total number of
significant digits in columns of type |NUMERIC|. `!None`
for other types.
5. `~psycopg2.extensions.Column.scale`: count of decimal digits in
the fractional part in columns of type |NUMERIC|. `!None`
for other types.
6. `~psycopg2.extensions.Column.null_ok`: always `!None` as not easy
to retrieve from the libpq.
.. versionchanged:: 2.4 .. versionchanged:: 2.4
if possible, columns descriptions are named tuple instead of if possible, columns descriptions are named tuple instead of
regular tuples. regular tuples.
.. versionchanged:: 2.8
columns descriptions are instances of `!Column`, exposing extra
attributes.
.. |NUMERIC| replace:: :sql:`NUMERIC`
.. method:: close() .. method:: close()
Close the cursor now (rather than whenever `del` is executed). Close the cursor now (rather than whenever `del` is executed).
@ -124,7 +111,7 @@ The ``cursor`` class
.. attribute:: name .. attribute:: name
Read-only attribute containing the name of the cursor if it was Read-only attribute containing the name of the cursor if it was
created as named cursor by `connection.cursor()`, or `!None` if creates as named cursor by `connection.cursor()`, or `!None` if
it is a client side cursor. See :ref:`server-side-cursors`. it is a client side cursor. See :ref:`server-side-cursors`.
.. extension:: .. extension::
@ -142,7 +129,7 @@ The ``cursor`` class
backward scroll (see the |declare-notes|__). backward scroll (see the |declare-notes|__).
.. |declare-notes| replace:: :sql:`DECLARE` notes .. |declare-notes| replace:: :sql:`DECLARE` notes
.. __: https://www.postgresql.org/docs/current/static/sql-declare.html#SQL-DECLARE-NOTES .. __: http://www.postgresql.org/docs/current/static/sql-declare.html#SQL-DECLARE-NOTES
.. note:: .. note::
@ -184,9 +171,9 @@ The ``cursor`` class
.. rubric:: Commands execution methods .. rubric:: Commands execution methods
.. method:: execute(query, vars=None) .. method:: execute(operation [, parameters])
Execute a database operation (query or command). Prepare and execute a database operation (query or command).
Parameters may be provided as sequence or mapping and will be bound to Parameters may be provided as sequence or mapping and will be bound to
variables in the operation. Variables are specified either with variables in the operation. Variables are specified either with
@ -197,10 +184,11 @@ The ``cursor`` class
values can be retrieved using |fetch*|_ methods. values can be retrieved using |fetch*|_ methods.
.. method:: executemany(query, vars_list) .. method:: executemany(operation, seq_of_parameters)
Execute a database operation (query or command) against all parameter Prepare a database operation (query or command) and then execute it
tuples or mappings found in the sequence *vars_list*. against all parameter tuples or mappings found in the sequence
`seq_of_parameters`.
The function is mostly useful for commands that update the database: The function is mostly useful for commands that update the database:
any result set returned by the query is discarded. any result set returned by the query is discarded.
@ -208,47 +196,18 @@ The ``cursor`` class
Parameters are bounded to the query using the same rules described in Parameters are bounded to the query using the same rules described in
the `~cursor.execute()` method. the `~cursor.execute()` method.
.. code:: python
>>> nums = ((1,), (5,), (10,))
>>> cur.executemany("INSERT INTO test (num) VALUES (%s)", nums)
>>> tuples = ((123, "foo"), (42, "bar"), (23, "baz"))
>>> cur.executemany("INSERT INTO test (num, data) VALUES (%s, %s)", tuples)
.. warning::
In its current implementation this method is not faster than
executing `~cursor.execute()` in a loop. For better performance
you can use the functions described in :ref:`fast-exec`.
.. method:: callproc(procname [, parameters]) .. method:: callproc(procname [, parameters])
Call a stored database procedure with the given name. The sequence of Call a stored database procedure with the given name. The sequence of
parameters must contain one entry for each argument that the procedure parameters must contain one entry for each argument that the procedure
expects. Overloaded procedures are supported. Named parameters can be expects. The result of the call is returned as modified copy of the
used by supplying the parameters as a dictionary. input sequence. Input parameters are left untouched, output and
input/output parameters replaced with possibly new values.
This function is, at present, not DBAPI-compliant. The return value is The procedure may also provide a result set as output. This must then
supposed to consist of the sequence of parameters with modified output be made available through the standard |fetch*|_ methods.
and input/output parameters. In future versions, the DBAPI-compliant
return value may be implemented, but for now the function returns None.
The procedure may provide a result set as output. This is then made
available through the standard |fetch*|_ methods.
.. versionchanged:: 2.7
added support for named arguments.
.. note::
`!callproc()` can only be used with PostgreSQL functions__, not
with the procedures__ introduced in PostgreSQL 11, which require
the :sql:`CALL` statement to run. Please use a normal
`execute()` to run them.
.. __: https://www.postgresql.org/docs/current/sql-createfunction.html
.. __: https://www.postgresql.org/docs/current/sql-createprocedure.html
.. method:: mogrify(operation [, parameters]) .. method:: mogrify(operation [, parameters])
@ -256,8 +215,6 @@ The ``cursor`` class
exactly the one that would be sent to the database running the exactly the one that would be sent to the database running the
`~cursor.execute()` method or similar. `~cursor.execute()` method or similar.
The returned string is always a bytes string.
>>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar')) >>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
"INSERT INTO test (num, data) VALUES (42, E'bar')" "INSERT INTO test (num, data) VALUES (42, E'bar')"
@ -292,7 +249,7 @@ The ``cursor`` class
>>> cur.execute("SELECT * FROM test;") >>> cur.execute("SELECT * FROM test;")
>>> for record in cur: >>> for record in cur:
... print(record) ... print record
... ...
(1, 100, "abc'def") (1, 100, "abc'def")
(2, None, 'dada') (2, None, 'dada')
@ -375,6 +332,10 @@ The ``cursor`` class
`~psycopg2.ProgrammingError` is raised and the cursor position is `~psycopg2.ProgrammingError` is raised and the cursor position is
not changed. not changed.
The method can be used both for client-side cursors and
:ref:`server-side cursors <server-side-cursors>`. Server-side cursors
can usually scroll backwards only if declared `~cursor.scrollable`.
.. note:: .. note::
According to the |DBAPI|_, the exception raised for a cursor out According to the |DBAPI|_, the exception raised for a cursor out
@ -386,13 +347,6 @@ The ``cursor`` class
except (ProgrammingError, IndexError), exc: except (ProgrammingError, IndexError), exc:
deal_with_it(exc) deal_with_it(exc)
The method can be used both for client-side cursors and
:ref:`server-side cursors <server-side-cursors>`. Server-side cursors
can usually scroll backwards only if declared `~cursor.scrollable`.
Moving out-of-bound in a server-side cursor doesn't result in an
exception, if the backend doesn't raise any (Postgres doesn't tell us
in a reliable way if we went out of bound).
.. attribute:: arraysize .. attribute:: arraysize
@ -461,17 +415,17 @@ The ``cursor`` class
more flexibility. more flexibility.
.. |CREATE-TABLE| replace:: :sql:`CREATE TABLE` .. |CREATE-TABLE| replace:: :sql:`CREATE TABLE`
.. __: https://www.postgresql.org/docs/current/static/sql-createtable.html .. __: http://www.postgresql.org/docs/current/static/sql-createtable.html
.. |INSERT-RETURNING| replace:: :sql:`INSERT ... RETURNING` .. |INSERT-RETURNING| replace:: :sql:`INSERT ... RETURNING`
.. __: https://www.postgresql.org/docs/current/static/sql-insert.html .. __: http://www.postgresql.org/docs/current/static/sql-insert.html
.. attribute:: query .. attribute:: query
Read-only attribute containing the body of the last query sent to the Read-only attribute containing the body of the last query sent to the
backend (including bound arguments) as bytes string. `!None` if no backend (including bound arguments). `!None` if no query has been
query has been executed yet: executed yet:
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar')) >>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
>>> cur.query >>> cur.query
@ -516,10 +470,8 @@ The ``cursor`` class
The time zone factory used to handle data types such as The time zone factory used to handle data types such as
:sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo` :sql:`TIMESTAMP WITH TIME ZONE`. It should be a `~datetime.tzinfo`
object. Default is `datetime.timezone`. object. A few implementations are available in the `psycopg2.tz`
module.
.. versionchanged:: 2.9
previosly the default factory was `psycopg2.tz.FixedOffsetTimezone`.
.. method:: nextset() .. method:: nextset()
@ -537,9 +489,6 @@ The ``cursor`` class
.. rubric:: COPY-related methods .. rubric:: COPY-related methods
Efficiently copy data from file-like objects to the database and back. See
:ref:`copy` for an overview.
.. extension:: .. extension::
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard. The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
@ -548,7 +497,7 @@ The ``cursor`` class
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None) .. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
Read data *from* the file-like object *file* appending them to Read data *from* the file-like object *file* appending them to
the table named *table*. the table named *table*. See :ref:`copy` for an overview.
:param file: file-like object to read data from. It must have both :param file: file-like object to read data from. It must have both
`!read()` and `!readline()` methods. `!read()` and `!readline()` methods.
@ -578,11 +527,6 @@ The ``cursor`` class
are encoded in the connection `~connection.encoding` when sent to are encoded in the connection `~connection.encoding` when sent to
the backend. the backend.
.. versionchanged:: 2.9
the table and fields names are now quoted. If you need to specify
a schema-qualified table please use `copy_expert()`.
.. method:: copy_to(file, table, sep='\\t', null='\\\\N', columns=None) .. method:: copy_to(file, table, sep='\\t', null='\\\\N', columns=None)
Write the content of the table named *table* *to* the file-like Write the content of the table named *table* *to* the file-like
@ -612,10 +556,6 @@ The ``cursor`` class
are decoded in the connection `~connection.encoding` when read are decoded in the connection `~connection.encoding` when read
from the backend. from the backend.
.. versionchanged:: 2.9
the table and fields names are now quoted. If you need to specify
a schema-qualified table please use `copy_expert()`.
.. method:: copy_expert(sql, file, size=8192) .. method:: copy_expert(sql, file, size=8192)
@ -630,10 +570,7 @@ The ``cursor`` class
The *sql* statement should be in the form :samp:`COPY {table} TO The *sql* statement should be in the form :samp:`COPY {table} TO
STDOUT` to export :samp:`{table}` to the *file* object passed as STDOUT` to export :samp:`{table}` to the *file* object passed as
argument or :samp:`COPY {table} FROM STDIN` to import the content of argument or :samp:`COPY {table} FROM STDIN` to import the content of
the *file* object into :samp:`{table}`. If you need to compose a the *file* object into :samp:`{table}`.
:sql:`COPY` statement dynamically (because table, fields, or query
parameters are in Python variables) you may use the objects provided
by the `psycopg2.sql` module.
*file* must be a readable file-like object (as required by *file* must be a readable file-like object (as required by
`~cursor.copy_from()`) for *sql* statement :sql:`COPY ... FROM STDIN` `~cursor.copy_from()`) for *sql* statement :sql:`COPY ... FROM STDIN`
@ -649,7 +586,7 @@ The ``cursor`` class
... ...
.. |COPY| replace:: :sql:`COPY` .. |COPY| replace:: :sql:`COPY`
.. __: https://www.postgresql.org/docs/current/static/sql-copy.html .. __: http://www.postgresql.org/docs/current/static/sql-copy.html
.. versionadded:: 2.0.6 .. versionadded:: 2.0.6
@ -658,24 +595,6 @@ The ``cursor`` class
using Unicode data instead of bytes. using Unicode data instead of bytes.
.. rubric:: Interoperation with other C API modules
.. attribute:: pgresult_ptr
Return the cursor's internal `!PGresult*` as integer. Useful to pass
the libpq raw result structure to C functions, e.g. via `ctypes`::
>>> import ctypes
>>> libpq = ctypes.pydll.LoadLibrary(ctypes.util.find_library('pq'))
>>> libpq.PQcmdStatus.argtypes = [ctypes.c_void_p]
>>> libpq.PQcmdStatus.restype = ctypes.c_char_p
>>> curs.execute("select 'x'")
>>> libpq.PQcmdStatus(curs.pgresult_ptr)
b'SELECT 1'
.. versionadded:: 2.8
.. testcode:: .. testcode::
:hide: :hide:

View File

@ -39,7 +39,7 @@ From PostgreSQL documentation:
.. seealso:: `PostgreSQL Error Codes table`__ .. seealso:: `PostgreSQL Error Codes table`__
.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE .. __: http://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
An example of the available constants defined in the module: An example of the available constants defined in the module:
@ -49,8 +49,8 @@ An example of the available constants defined in the module:
>>> errorcodes.UNDEFINED_TABLE >>> errorcodes.UNDEFINED_TABLE
'42P01' '42P01'
Constants representing all the error values defined by PostgreSQL versions Constants representing all the error values documented by PostgreSQL versions
between 8.1 and 15 are included in the module. between 8.1 and 9.2 are included in the module.
.. autofunction:: lookup(code) .. autofunction:: lookup(code)
@ -59,7 +59,7 @@ between 8.1 and 15 are included in the module.
>>> try: >>> try:
... cur.execute("SELECT ouch FROM aargh;") ... cur.execute("SELECT ouch FROM aargh;")
... except Exception as e: ... except Exception, e:
... pass ... pass
... ...
>>> errorcodes.lookup(e.pgcode[:2]) >>> errorcodes.lookup(e.pgcode[:2])

View File

@ -1,89 +0,0 @@
`psycopg2.errors` -- Exception classes mapping PostgreSQL errors
================================================================
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
.. index::
single: Error; Class
.. module:: psycopg2.errors
.. versionadded:: 2.8
.. versionchanged:: 2.8.4 added errors introduced in PostgreSQL 12
.. versionchanged:: 2.8.6 added errors introduced in PostgreSQL 13
.. versionchanged:: 2.9.2 added errors introduced in PostgreSQL 14
.. versionchanged:: 2.9.4 added errors introduced in PostgreSQL 15
.. versionchanged:: 2.9.10 added errors introduced in PostgreSQL 17
This module exposes the classes psycopg raises upon receiving an error from
the database with a :sql:`SQLSTATE` value attached (available in the
`~psycopg2.Error.pgcode` attribute). The content of the module is generated
from the PostgreSQL source code and includes classes for every error defined
by PostgreSQL in versions between 9.1 and 15.
Every class in the module is named after what referred as "condition name" `in
the documentation`__, converted to CamelCase: e.g. the error 22012,
``division_by_zero`` is exposed by this module as the class `!DivisionByZero`.
.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
Every exception class is a subclass of one of the :ref:`standard DB-API
exception <dbapi-exceptions>` and expose the `~psycopg2.Error` interface.
Each class' superclass is what used to be raised by psycopg in versions before
the introduction of this module, so everything should be compatible with
previously written code catching one the DB-API class: if your code used to
catch `!IntegrityError` to detect a duplicate entry, it will keep on working
even if a more specialised subclass such as `UniqueViolation` is raised.
The new classes allow a more idiomatic way to check and process a specific
error among the many the database may return. For instance, in order to check
that a table is locked, the following code could have been used previously:
.. code-block:: python
try:
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
except psycopg2.OperationalError as e:
if e.pgcode == psycopg2.errorcodes.LOCK_NOT_AVAILABLE:
locked = True
else:
raise
While this method is still available, the specialised class allows for a more
idiomatic error handler:
.. code-block:: python
try:
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
except psycopg2.errors.LockNotAvailable:
locked = True
.. autofunction:: lookup
.. code-block:: python
try:
cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
except psycopg2.errors.lookup("55P03"):
locked = True
SQLSTATE exception classes
--------------------------
The following table contains the list of all the SQLSTATE classes exposed by
the module.
Note that, for completeness, the module also exposes all the
:ref:`DB-API-defined exceptions <dbapi-exceptions>` and :ref:`a few
psycopg-specific ones <extension-exceptions>` exposed by the `!extensions`
module, which are not listed here.
.. include:: sqlstate_errors.rst

View File

@ -12,12 +12,6 @@
The module contains a few objects and function extending the minimum set of The module contains a few objects and function extending the minimum set of
functionalities defined by the |DBAPI|_. functionalities defined by the |DBAPI|_.
Classes definitions
-------------------
Instances of these classes are usually returned by factory functions or
attributes. Their definitions are exposed here to allow subclassing,
introspection etc.
.. class:: connection(dsn, async=False) .. class:: connection(dsn, async=False)
@ -29,12 +23,9 @@ introspection etc.
For a complete description of the class, see `connection`. For a complete description of the class, see `connection`.
.. versionchanged:: 2.7
*async_* can be used as alias for *async*.
.. class:: cursor(conn, name=None) .. class:: cursor(conn, name=None)
It is the class usually returned by the `connection.cursor()` It is the class usually returnded by the `connection.cursor()`
method. It is exposed by the `extensions` module in order to allow method. It is exposed by the `extensions` module in order to allow
subclassing to extend its behaviour: the subclass should be passed to the subclassing to extend its behaviour: the subclass should be passed to the
`!cursor()` method using the `cursor_factory` parameter. See `!cursor()` method using the `cursor_factory` parameter. See
@ -42,7 +33,6 @@ introspection etc.
For a complete description of the class, see `cursor`. For a complete description of the class, see `cursor`.
.. class:: lobject(conn [, oid [, mode [, new_oid [, new_file ]]]]) .. class:: lobject(conn [, oid [, mode [, new_oid [, new_file ]]]])
Wrapper for a PostgreSQL large object. See :ref:`large-objects` for an Wrapper for a PostgreSQL large object. See :ref:`large-objects` for an
@ -57,13 +47,11 @@ introspection etc.
Database OID of the object. Database OID of the object.
.. attribute:: mode .. attribute:: mode
The mode the database was open. See `connection.lobject()` for a The mode the database was open. See `connection.lobject()` for a
description of the available modes. description of the available modes.
.. method:: read(bytes=-1) .. method:: read(bytes=-1)
Read a chunk of data from the current file position. If -1 (default) Read a chunk of data from the current file position. If -1 (default)
@ -76,7 +64,6 @@ introspection etc.
.. versionchanged:: 2.4 .. versionchanged:: 2.4
added Unicode support. added Unicode support.
.. method:: write(str) .. method:: write(str)
Write a string to the large object. Return the number of bytes Write a string to the large object. Return the number of bytes
@ -86,7 +73,6 @@ introspection etc.
.. versionchanged:: 2.4 .. versionchanged:: 2.4
added Unicode support. added Unicode support.
.. method:: export(file_name) .. method:: export(file_name)
Export the large object content to the file system. Export the large object content to the file system.
@ -94,52 +80,35 @@ introspection etc.
The method uses the efficient |lo_export|_ libpq function. The method uses the efficient |lo_export|_ libpq function.
.. |lo_export| replace:: `!lo_export()` .. |lo_export| replace:: `!lo_export()`
.. _lo_export: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT .. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
.. method:: seek(offset, whence=0) .. method:: seek(offset, whence=0)
Set the lobject current position. Set the lobject current position.
.. versionchanged:: 2.6
added support for *offset* > 2GB.
.. method:: tell() .. method:: tell()
Return the lobject current position. Return the lobject current position.
.. versionadded:: 2.2
.. versionchanged:: 2.6
added support for return value > 2GB.
.. method:: truncate(len=0) .. method:: truncate(len=0)
.. versionadded:: 2.2.0
Truncate the lobject to the given size. Truncate the lobject to the given size.
The method will only be available if Psycopg has been built against The method will only be available if Psycopg has been built against libpq
libpq from PostgreSQL 8.3 or later and can only be used with from PostgreSQL 8.3 or later and can only be used with PostgreSQL servers
PostgreSQL servers running these versions. It uses the |lo_truncate|_ running these versions. It uses the |lo_truncate|_ libpq function.
libpq function.
.. |lo_truncate| replace:: `!lo_truncate()` .. |lo_truncate| replace:: `!lo_truncate()`
.. _lo_truncate: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE .. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
.. versionadded:: 2.2 .. warning::
.. versionchanged:: 2.6
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() .. method:: close()
@ -154,130 +123,6 @@ introspection etc.
Close the object and remove it from the database. Close the object and remove it from the database.
.. autoclass:: ConnectionInfo(connection)
.. versionadded:: 2.8
.. autoattribute:: dbname
.. autoattribute:: user
.. autoattribute:: password
.. autoattribute:: host
.. autoattribute:: port
.. autoattribute:: options
.. autoattribute:: dsn_parameters
Example::
>>> conn.info.dsn_parameters
{'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'}
Requires libpq >= 9.3.
.. autoattribute:: status
.. autoattribute:: transaction_status
.. automethod:: parameter_status(name)
.. autoattribute:: protocol_version
Currently Psycopg supports only protocol 3, which allows connection
to PostgreSQL server from version 7.4. Psycopg versions previous than
2.3 support both protocols 2 and 3.
.. autoattribute:: server_version
The number is formed by converting the major, minor, and revision
numbers into two-decimal-digit numbers and appending them together.
After PostgreSQL 10 the minor version was dropped, so the second group
of digits is always ``00``. For example, version 9.3.5 will be
returned as ``90305``, version 10.2 as ``100002``.
.. autoattribute:: error_message
.. autoattribute:: socket
.. autoattribute:: backend_pid
.. autoattribute:: needs_password
.. autoattribute:: used_password
.. autoattribute:: ssl_in_use
.. automethod:: ssl_attribute(name)
.. autoattribute:: ssl_attribute_names
.. class:: Column(\*args, \*\*kwargs)
Description of one result column, exposed as items of the
`cursor.description` sequence.
.. versionadded:: 2.8
in previous version the `!description` attribute was a sequence of
simple tuples or namedtuples.
.. attribute:: name
The name of the column returned.
.. attribute:: type_code
The PostgreSQL OID of the column. You can use the |pg_type|_ system
table to get more informations about the type. This is the value used
by Psycopg to decide what Python type use to represent the value. See
also :ref:`type-casting-from-sql-to-python`.
.. attribute:: display_size
Supposed to be the actual length of the column in bytes. Obtaining
this value is computationally intensive, so it is always `!None`.
.. versionchanged:: 2.8
It was previously possible to obtain this value using a compiler
flag at builtin.
.. attribute:: internal_size
The size in bytes of the column associated to this column on the
server. Set to a negative value for variable-size types See also
PQfsize_.
.. attribute:: precision
Total number of significant digits in columns of type |NUMERIC|_.
`!None` for other types.
.. attribute:: scale
Count of decimal digits in the fractional part in columns of type
|NUMERIC|. `!None` for other types.
.. attribute:: null_ok
Always `!None` as not easy to retrieve from the libpq.
.. attribute:: table_oid
The oid of the table from which the column was fetched (matching
:sql:`pg_class.oid`). `!None` if the column is not a simple reference
to a table column. See also PQftable_.
.. versionadded:: 2.8
.. attribute:: table_column
The number of the column (within its table) making up the result
(matching :sql:`pg_attribute.attnum`, so it will start from 1).
`!None` if the column is not a simple reference to a table column. See
also PQftablecol_.
.. versionadded:: 2.8
.. |pg_type| replace:: :sql:`pg_type`
.. _pg_type: https://www.postgresql.org/docs/current/static/catalog-pg-type.html
.. _PQgetlength: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQGETLENGTH
.. _PQfsize: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFSIZE
.. _PQftable: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFTABLE
.. _PQftablecol: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQFTABLECOL
.. _NUMERIC: https://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL
.. |NUMERIC| replace:: :sql:`NUMERIC`
.. autoclass:: Notify(pid, channel, payload='') .. autoclass:: Notify(pid, channel, payload='')
:members: pid, channel, payload :members: pid, channel, payload
@ -310,7 +155,6 @@ introspection etc.
message_primary message_primary
schema_name schema_name
severity severity
severity_nonlocalized
source_file source_file
source_function source_function
source_line source_line
@ -323,9 +167,14 @@ introspection etc.
not all the fields are available for all the errors and for all the not all the fields are available for all the errors and for all the
server versions. server versions.
.. versionadded:: 2.8
The `!severity_nonlocalized` attribute.
.. autofunction:: set_wait_callback(f)
.. versionadded:: 2.2.0
.. autofunction:: get_wait_callback()
.. versionadded:: 2.2.0
.. _sql-adaptation-objects: .. _sql-adaptation-objects:
@ -340,7 +189,7 @@ deal with Python objects adaptation:
.. function:: adapt(obj) .. function:: adapt(obj)
Return the SQL representation of *obj* as an `ISQLQuote`. Raise a Return the SQL representation of *obj* as a string. Raise a
`~psycopg2.ProgrammingError` if how to adapt the object is unknown. `~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 In order to allow new objects to be adapted, register a new adapter for it
using the `register_adapter()` function. using the `register_adapter()` function.
@ -354,7 +203,7 @@ deal with Python objects adaptation:
Register a new adapter for the objects of class *class*. Register a new adapter for the objects of class *class*.
*adapter* should be a function taking a single argument (the object *adapter* should be a function taking a single argument (the object
to adapt) and returning an object conforming to the `ISQLQuote` to adapt) and returning an object conforming the `ISQLQuote`
protocol (e.g. exposing a `!getquoted()` method). The `AsIs` is protocol (e.g. exposing a `!getquoted()` method). The `AsIs` is
often useful for this task. often useful for this task.
@ -413,9 +262,9 @@ deal with Python objects adaptation:
.. method:: getquoted() .. method:: getquoted()
Return the string enclosed in single quotes. Any single quote appearing Return the string enclosed in single quotes. Any single quote
in the string is escaped by doubling it according to SQL string appearing in the the string is escaped by doubling it according to SQL
constants syntax. Backslashes are escaped too. string constants syntax. Backslashes are escaped too.
>>> QuotedString(r"O'Reilly").getquoted() >>> QuotedString(r"O'Reilly").getquoted()
"'O''Reilly'" "'O''Reilly'"
@ -453,6 +302,13 @@ deal with Python objects adaptation:
Specialized adapters for Python datetime objects. Specialized adapters for Python datetime objects.
.. class:: DateFromMx
TimeFromMx
TimestampFromMx
IntervalFromMx
Specialized adapters for `mx.DateTime`_ objects.
.. data:: adapters .. data:: adapters
Dictionary of the currently registered object adapters. Use Dictionary of the currently registered object adapters. Use
@ -496,8 +352,8 @@ details.
`register_type()` to be used. `register_type()` to be used.
:param oids: tuple of OIDs of the PostgreSQL type to convert. It should :param oids: tuple of OIDs of the PostgreSQL type to convert. It should
probably contain the oid of the array type (e.g. the ``typarray`` probably be the oid of the array type (e.g. the ``typarray`` field in
field in the ``pg_type`` table). the ``pg_type`` table.
:param name: the name of the new type adapter. :param name: the name of the new type adapter.
:param base_caster: a Psycopg typecaster, e.g. created using the :param base_caster: a Psycopg typecaster, e.g. created using the
`new_type()` function. The caster should be able to parse a single `new_type()` function. The caster should be able to parse a single
@ -510,12 +366,11 @@ details.
.. note:: .. note::
The function can be used to create a generic array typecaster, The function can be used to create a generic array typecaster,
returning a list of strings: just use `psycopg2.STRING` as base returning a list of strings: just use the `~psycopg2.STRING` as base
typecaster. For instance, if you want to receive an array of typecaster. For instance, if you want to receive from the database an
:sql:`macaddr` from the database, each address represented by string, array of :sql:`macaddr`, each address represented by string, you can
you can use:: use::
# select typarray from pg_type where typname = 'macaddr' -> 1040
psycopg2.extensions.register_type( psycopg2.extensions.register_type(
psycopg2.extensions.new_array_type( psycopg2.extensions.new_array_type(
(1040,), 'MACADDR[]', psycopg2.STRING)) (1040,), 'MACADDR[]', psycopg2.STRING))
@ -540,31 +395,24 @@ details.
.. data:: encodings .. data:: encodings
Mapping from `PostgreSQL encoding`__ to `Python encoding`__ names. Mapping from `PostgreSQL encoding`__ names to `Python codec`__ names.
Used by Psycopg when adapting or casting unicode strings. See Used by Psycopg when adapting or casting unicode strings. See
:ref:`unicode-handling`. :ref:`unicode-handling`.
.. __: https://www.postgresql.org/docs/current/static/multibyte.html .. __: http://www.postgresql.org/docs/current/static/multibyte.html
.. __: https://docs.python.org/library/codecs.html#standard-encodings .. __: http://docs.python.org/library/codecs.html#standard-encodings
.. index:: .. index::
single: Exceptions; Additional single: Exceptions; Additional
.. _extension-exceptions:
Additional exceptions Additional exceptions
--------------------- ---------------------
The module exports a few exceptions in addition to the :ref:`standard ones The module exports a few exceptions in addition to the :ref:`standard ones
<dbapi-exceptions>` defined by the |DBAPI|_. <dbapi-exceptions>` defined by the |DBAPI|_.
.. note::
From psycopg 2.8 these error classes are also exposed by the
`psycopg2.errors` module.
.. exception:: QueryCanceledError .. exception:: QueryCanceledError
(subclasses `~psycopg2.OperationalError`) (subclasses `~psycopg2.OperationalError`)
@ -579,143 +427,13 @@ The module exports a few exceptions in addition to the :ref:`standard ones
(subclasses `~psycopg2.OperationalError`) (subclasses `~psycopg2.OperationalError`)
Error causing transaction rollback (deadlocks, serialization failures, Error causing transaction rollback (deadlocks, serialisation failures,
etc). It can be trapped specifically to detect a deadlock. etc). It can be trapped specifically to detect a deadlock.
.. versionadded:: 2.0.7 .. versionadded:: 2.0.7
.. _coroutines-functions:
Coroutines support functions
----------------------------
These functions are used to set and retrieve the callback function for
:ref:`cooperation with coroutine libraries <green-support>`.
.. versionadded:: 2.2
.. autofunction:: set_wait_callback(f)
.. autofunction:: get_wait_callback()
Other functions
---------------
.. 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).
.. versionadded:: 2.7
.. seealso:: libpq docs for `PQlibVersion()`__.
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQLIBVERSION
.. function:: make_dsn(dsn=None, \*\*kwargs)
Create a valid connection string from arguments.
Put together the arguments in *kwargs* into a connection string. If *dsn*
is specified too, merge the arguments coming from both the sources. If the
same argument name is specified in both the sources, the *kwargs* value
overrides the *dsn* value.
The input arguments are validated: the output should always be a valid
connection string (as far as `parse_dsn()` is concerned). If not raise
`~psycopg2.ProgrammingError`.
Example::
>>> from psycopg2.extensions import make_dsn
>>> make_dsn('dbname=foo host=example.com', password="s3cr3t")
'host=example.com password=s3cr3t dbname=foo'
.. versionadded:: 2.7
.. function:: parse_dsn(dsn)
Parse connection string into a dictionary of keywords and values.
Parsing is delegated to the libpq: different versions of the client
library may support different formats or parameters (for example,
`connection URIs`__ are only supported from libpq 9.2). Raise
`~psycopg2.ProgrammingError` if the *dsn* is not valid.
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
Example::
>>> from psycopg2.extensions import parse_dsn
>>> parse_dsn('dbname=test user=postgres password=secret')
{'password': 'secret', 'user': 'postgres', 'dbname': 'test'}
>>> parse_dsn("postgresql://someone@example.com/somedb?connect_timeout=10")
{'host': 'example.com', 'user': 'someone', 'dbname': 'somedb', 'connect_timeout': '10'}
.. versionadded:: 2.7
.. seealso:: libpq docs for `PQconninfoParse()`__.
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFOPARSE
.. 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.
.. versionadded:: 2.7
.. seealso:: libpq docs for `PQescapeIdentifier()`__
.. __: https://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQESCAPEIDENTIFIER
.. method:: encrypt_password(password, user, scope=None, algorithm=None)
Return the encrypted form of a PostgreSQL password.
:param password: the cleartext password to encrypt
:param user: the name of the user to use the password for
:param scope: the scope to encrypt the password into; if *algorithm* is
``md5`` it can be `!None`
:type scope: `connection` or `cursor`
:param algorithm: the password encryption algorithm to use
The *algorithm* ``md5`` is always supported. Other algorithms are only
supported if the client libpq version is at least 10 and may require a
compatible server version: check the `PostgreSQL encryption
documentation`__ to know the algorithms supported by your server.
.. __: https://www.postgresql.org/docs/current/static/encryption-options.html
Using `!None` as *algorithm* will result in querying the server to know the
current server password encryption setting, which is a blocking operation:
query the server separately and specify a value for *algorithm* if you
want to maintain a non-blocking behaviour.
.. versionadded:: 2.8
.. seealso:: PostgreSQL docs for the `password_encryption`__ setting, libpq `PQencryptPasswordConn()`__, `PQencryptPassword()`__ functions.
.. __: https://www.postgresql.org/docs/current/static/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORD
.. index:: .. index::
pair: Isolation level; Constants pair: Isolation level; Constants
@ -725,16 +443,15 @@ Isolation level constants
------------------------- -------------------------
Psycopg2 `connection` objects hold informations about the PostgreSQL Psycopg2 `connection` objects hold informations about the PostgreSQL
`transaction isolation level`_. By default Psycopg doesn't change the default `transaction isolation level`_. The current transaction level can be read
configuration of the server (`ISOLATION_LEVEL_DEFAULT`); the default for from the `~connection.isolation_level` attribute. The default isolation
PostgreSQL servers is typically :sql:`READ COMMITTED`, but this may be changed level is :sql:`READ COMMITTED`. A different isolation level con be set
in the server configuration files. A different isolation level can be set through the `~connection.set_isolation_level()` method. The level can be
through the `~connection.set_isolation_level()` or `~connection.set_session()` set to one of the following constants:
methods. The level can be set to one of the following constants:
.. data:: ISOLATION_LEVEL_AUTOCOMMIT .. data:: ISOLATION_LEVEL_AUTOCOMMIT
No transaction is started when commands are executed and no No transaction is started when command are issued and no
`~connection.commit()` or `~connection.rollback()` is required. `~connection.commit()` or `~connection.rollback()` is required.
Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM` Some PostgreSQL command such as :sql:`CREATE DATABASE` or :sql:`VACUUM`
can't run into a transaction: to run such command use:: can't run into a transaction: to run such command use::
@ -751,8 +468,8 @@ methods. The level can be set to one of the following constants:
.. data:: ISOLATION_LEVEL_READ_COMMITTED .. data:: ISOLATION_LEVEL_READ_COMMITTED
This is usually the default PostgreSQL value, but a different default may This is usually the the default PostgreSQL value, but a different default
be set in the database configuration. may be set in the database configuration.
A new transaction is started at the first `~cursor.execute()` command on a A new transaction is started at the first `~cursor.execute()` command on a
cursor and at each new `!execute()` after a `~connection.commit()` or a cursor and at each new `!execute()` after a `~connection.commit()` or a
@ -765,7 +482,7 @@ methods. The level can be set to one of the following constants:
.. seealso:: `Read Committed Isolation Level`__ in PostgreSQL .. seealso:: `Read Committed Isolation Level`__ in PostgreSQL
documentation. documentation.
.. __: https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED .. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-READ-COMMITTED
.. data:: ISOLATION_LEVEL_REPEATABLE_READ .. data:: ISOLATION_LEVEL_REPEATABLE_READ
@ -789,7 +506,7 @@ methods. The level can be set to one of the following constants:
.. seealso:: `Repeatable Read Isolation Level`__ in PostgreSQL .. seealso:: `Repeatable Read Isolation Level`__ in PostgreSQL
documentation. documentation.
.. __: https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-REPEATABLE-READ .. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-REPEATABLE-READ
.. data:: ISOLATION_LEVEL_SERIALIZABLE .. data:: ISOLATION_LEVEL_SERIALIZABLE
@ -798,7 +515,7 @@ methods. The level can be set to one of the following constants:
:sql:`SERIALIZABLE` isolation level. This is the strictest transactions :sql:`SERIALIZABLE` isolation level. This is the strictest transactions
isolation level, equivalent to having the transactions executed serially isolation level, equivalent to having the transactions executed serially
rather than concurrently. However applications using this level must be rather than concurrently. However applications using this level must be
prepared to retry transactions due to serialization failures. prepared to retry reansactions due to serialization failures.
Starting from PostgreSQL 9.1, this mode monitors for conditions which Starting from PostgreSQL 9.1, this mode monitors for conditions which
could make execution of a concurrent set of serializable transactions could make execution of a concurrent set of serializable transactions
@ -808,18 +525,8 @@ methods. The level can be set to one of the following constants:
.. seealso:: `Serializable Isolation Level`__ in PostgreSQL documentation. .. seealso:: `Serializable Isolation Level`__ in PostgreSQL documentation.
.. __: https://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE .. __: http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE
.. data:: ISOLATION_LEVEL_DEFAULT
A new transaction is started at the first `~cursor.execute()` command, but
the isolation level is not explicitly selected by Psycopg: the server will
use whatever level is defined in its configuration or by statements
executed within the session outside Pyscopg control. If you want to know
what the value is you can use a query such as :sql:`show
transaction_isolation`.
.. versionadded:: 2.7
.. index:: .. index::
@ -831,7 +538,7 @@ Transaction status constants
---------------------------- ----------------------------
These values represent the possible status of a transaction: the current value These values represent the possible status of a transaction: the current value
can be read using the `connection.info.transaction_status` property. can be read using the `connection.get_transaction_status()` method.
.. data:: TRANSACTION_STATUS_IDLE .. data:: TRANSACTION_STATUS_IDLE
@ -902,7 +609,7 @@ internal usage and Python code should not rely on them.
Poll constants Poll constants
-------------- --------------
.. versionadded:: 2.2 .. versionadded:: 2.2.0
These values can be returned by `connection.poll()` during asynchronous These values can be returned by `connection.poll()` during asynchronous
connection and communication. They match the values in the libpq enum connection and communication. They match the values in the libpq enum
@ -951,7 +658,6 @@ Python objects. All the typecasters are automatically registered, except
from the database. See :ref:`unicode-handling` for details. from the database. See :ref:`unicode-handling` for details.
.. data:: BOOLEAN .. data:: BOOLEAN
BYTES
DATE DATE
DECIMAL DECIMAL
FLOAT FLOAT
@ -968,7 +674,6 @@ from the database. See :ref:`unicode-handling` for details.
.. data:: BINARYARRAY .. data:: BINARYARRAY
BOOLEANARRAY BOOLEANARRAY
BYTESARRAY
DATEARRAY DATEARRAY
DATETIMEARRAY DATETIMEARRAY
DECIMALARRAY DECIMALARRAY
@ -985,26 +690,31 @@ from the database. See :ref:`unicode-handling` for details.
.. data:: PYDATE .. data:: PYDATE
PYDATETIME PYDATETIME
PYDATETIMETZ
PYINTERVAL PYINTERVAL
PYTIME PYTIME
PYDATEARRAY PYDATEARRAY
PYDATETIMEARRAY PYDATETIMEARRAY
PYDATETIMETZARRAY
PYINTERVALARRAY PYINTERVALARRAY
PYTIMEARRAY PYTIMEARRAY
Typecasters to convert time-related data types to Python `!datetime` Typecasters to convert time-related data types to Python `!datetime`
objects. objects.
.. versionchanged:: 2.2 .. data:: MXDATE
MXDATETIME
MXINTERVAL
MXTIME
MXDATEARRAY
MXDATETIMEARRAY
MXINTERVALARRAY
MXTIMEARRAY
Typecasters to convert time-related data types to `mx.DateTime`_ objects.
Only available if Psycopg was compiled with `!mx` support.
.. versionchanged:: 2.2.0
previously the `DECIMAL` typecaster and the specific time-related previously the `DECIMAL` typecaster and the specific time-related
typecasters (`!PY*` and `!MX*`) were not exposed by the `extensions` typecasters (`!PY*` and `!MX*`) were not exposed by the `extensions`
module. In older versions they can be imported from the implementation module. In older versions they can be imported from the implementation
module `!psycopg2._psycopg`. module `!psycopg2._psycopg`.
.. versionadded:: 2.7.2
the `!*DATETIMETZ*` objects.
.. versionadded:: 2.8
the `!BYTES` and `BYTESARRAY` objects.

View File

@ -41,8 +41,8 @@ If you want to use a `!connection` subclass you can pass it as the
Dictionary-like cursor Dictionary-like cursor
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
The dict cursors allow to access to the attributes of retrieved records The dict cursors allow to access to the retrieved records using an iterface
using an interface similar to the Python dictionaries instead of the tuples. similar to the Python dictionaries instead of the tuples.
>>> dict_cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) >>> dict_cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
>>> dict_cur.execute("INSERT INTO test (num, data) VALUES(%s, %s)", >>> dict_cur.execute("INSERT INTO test (num, data) VALUES(%s, %s)",
@ -99,6 +99,20 @@ Real dictionary cursor
.. versionadded:: 2.3 .. versionadded:: 2.3
These objects require :py:func:`collections.namedtuple` to be found, so it is
available out-of-the-box only from Python 2.6. Anyway, the namedtuple
implementation is compatible with previous Python versions, so all you
have to do is to `download it`__ and make it available where we
expect it to be... ::
from somewhere import namedtuple
import collections
collections.namedtuple = namedtuple
from psycopg.extras import NamedTupleConnection
# ...
.. __: http://code.activestate.com/recipes/500261-named-tuples/
.. autoclass:: NamedTupleCursor .. autoclass:: NamedTupleCursor
.. autoclass:: NamedTupleConnection .. autoclass:: NamedTupleConnection
@ -122,11 +136,6 @@ Logging cursor
.. autoclass:: LoggingCursor .. autoclass:: LoggingCursor
.. note::
Queries that are executed with `cursor.executemany()` are not logged.
.. autoclass:: MinTimeLoggingConnection .. autoclass:: MinTimeLoggingConnection
:members: initialize,filter :members: initialize,filter
@ -134,417 +143,6 @@ Logging cursor
.. _replication-objects:
Replication support objects
---------------------------
See :ref:`replication-support` for an introduction to the topic.
The following replication types are defined:
.. data:: REPLICATION_LOGICAL
.. data:: REPLICATION_PHYSICAL
.. index::
pair: Connection; replication
.. autoclass:: LogicalReplicationConnection
This connection factory class can be used to open a special type of
connection that is used for logical replication.
Example::
from psycopg2.extras import LogicalReplicationConnection
log_conn = psycopg2.connect(dsn, connection_factory=LogicalReplicationConnection)
log_cur = log_conn.cursor()
.. autoclass:: PhysicalReplicationConnection
This connection factory class can be used to open a special type of
connection that is used for physical replication.
Example::
from psycopg2.extras import PhysicalReplicationConnection
phys_conn = psycopg2.connect(dsn, connection_factory=PhysicalReplicationConnection)
phys_cur = phys_conn.cursor()
Both `LogicalReplicationConnection` and `PhysicalReplicationConnection` use
`ReplicationCursor` for actual communication with the server.
.. index::
pair: Message; replication
The individual messages in the replication stream are represented by
`ReplicationMessage` objects (both logical and physical type):
.. autoclass:: ReplicationMessage
.. attribute:: payload
The actual data received from the server.
An instance of either `bytes()` or `unicode()`, depending on the value
of `decode` option passed to `~ReplicationCursor.start_replication()`
on the connection. See `~ReplicationCursor.read_message()` for
details.
.. attribute:: data_size
The raw size of the message payload (before possible unicode
conversion).
.. attribute:: data_start
LSN position of the start of the message.
.. attribute:: wal_end
LSN position of the current end of WAL on the server.
.. attribute:: send_time
A `~datetime` object representing the server timestamp at the moment
when the message was sent.
.. attribute:: cursor
A reference to the corresponding `ReplicationCursor` object.
.. index::
pair: Cursor; replication
.. autoclass:: ReplicationCursor
.. method:: create_replication_slot(slot_name, slot_type=None, output_plugin=None)
Create streaming replication slot.
:param slot_name: name of the replication slot to be created
:param slot_type: type of replication: should be either
`REPLICATION_LOGICAL` or `REPLICATION_PHYSICAL`
:param output_plugin: name of the logical decoding output plugin to be
used by the slot; required for logical
replication connections, disallowed for physical
Example::
log_cur.create_replication_slot("logical1", "test_decoding")
phys_cur.create_replication_slot("physical1")
# either logical or physical replication connection
cur.create_replication_slot("slot1", slot_type=REPLICATION_LOGICAL)
When creating a slot on a logical replication connection, a logical
replication slot is created by default. Logical replication requires
name of the logical decoding output plugin to be specified.
When creating a slot on a physical replication connection, a physical
replication slot is created by default. No output plugin parameter is
required or allowed when creating a physical replication slot.
In either case the type of slot being created can be specified
explicitly using *slot_type* parameter.
Replication slots are a feature of PostgreSQL server starting with
version 9.4.
.. method:: drop_replication_slot(slot_name)
Drop streaming replication slot.
:param slot_name: name of the replication slot to drop
Example::
# either logical or physical replication connection
cur.drop_replication_slot("slot1")
Replication slots are a feature of PostgreSQL server starting with
version 9.4.
.. method:: start_replication(slot_name=None, slot_type=None, start_lsn=0, timeline=0, options=None, decode=False, status_interval=10)
Start replication on the connection.
:param slot_name: name of the replication slot to use; required for
logical replication, physical replication can work
with or without a slot
:param slot_type: type of replication: should be either
`REPLICATION_LOGICAL` or `REPLICATION_PHYSICAL`
:param start_lsn: the optional LSN position to start replicating from,
can be an integer or a string of hexadecimal digits
in the form ``XXX/XXX``
:param timeline: WAL history timeline to start streaming from (optional,
can only be used with physical replication)
:param options: a dictionary of options to pass to logical replication
slot (not allowed with physical replication)
:param decode: a flag indicating that unicode conversion should be
performed on messages received from the server
:param status_interval: time between feedback packets sent to the server
If a *slot_name* is specified, the slot must exist on the server and
its type must match the replication type used.
If not specified using *slot_type* parameter, the type of replication
is defined by the type of replication connection. Logical replication
is only allowed on logical replication connection, but physical
replication can be used with both types of connection.
On the other hand, physical replication doesn't require a named
replication slot to be used, only logical replication does. In any
case logical replication and replication slots are a feature of
PostgreSQL server starting with version 9.4. Physical replication can
be used starting with 9.0.
If *start_lsn* is specified, the requested stream will start from that
LSN. The default is `!None` which passes the LSN ``0/0`` causing
replay to begin at the last point for which the server got flush
confirmation from the client, or the oldest available point for a new
slot.
The server might produce an error if a WAL file for the given LSN has
already been recycled or it may silently start streaming from a later
position: the client can verify the actual position using information
provided by the `ReplicationMessage` attributes. The exact server
behavior depends on the type of replication and use of slots.
The *timeline* parameter can only be specified with physical
replication and only starting with server version 9.3.
A dictionary of *options* may be passed to the logical decoding plugin
on a logical replication slot. The set of supported options depends
on the output plugin that was used to create the slot. Must be
`!None` for physical replication.
If *decode* is set to `!True` the messages received from the server
would be converted according to the connection `~connection.encoding`.
*This parameter should not be set with physical replication or with
logical replication plugins that produce binary output.*
Replication stream should periodically send feedback to the database
to prevent disconnect via timeout. Feedback is automatically sent when
`read_message()` is called or during run of the `consume_stream()`.
To specify the feedback interval use *status_interval* parameter.
The value of this parameter must be set to at least 1 second, but
it can have a fractional part.
This function constructs a |START_REPLICATION|_ command and calls
`start_replication_expert()` internally.
After starting the replication, to actually consume the incoming
server messages use `consume_stream()` or implement a loop around
`read_message()` in case of :ref:`asynchronous connection
<async-support>`.
.. versionchanged:: 2.8.3
added the *status_interval* parameter.
.. |START_REPLICATION| replace:: :sql:`START_REPLICATION`
.. _START_REPLICATION: https://www.postgresql.org/docs/current/static/protocol-replication.html
.. method:: start_replication_expert(command, decode=False, status_interval=10)
Start replication on the connection using provided
|START_REPLICATION|_ command.
:param command: The full replication command. It can be a string or a
`~psycopg2.sql.Composable` instance for dynamic generation.
:param decode: a flag indicating that unicode conversion should be
performed on messages received from the server.
:param status_interval: time between feedback packets sent to the server
.. versionchanged:: 2.8.3
added the *status_interval* parameter.
.. method:: consume_stream(consume, keepalive_interval=None)
:param consume: a callable object with signature :samp:`consume({msg})`
:param keepalive_interval: interval (in seconds) to send keepalive
messages to the server
This method can only be used with synchronous connection. For
asynchronous connections see `read_message()`.
Before using this method to consume the stream call
`start_replication()` first.
This method enters an endless loop reading messages from the server
and passing them to ``consume()`` one at a time, then waiting for more
messages from the server. In order to make this method break out of
the loop and return, ``consume()`` can throw a `StopReplication`
exception. Any unhandled exception will make it break out of the loop
as well.
The *msg* object passed to ``consume()`` is an instance of
`ReplicationMessage` class. See `read_message()` for details about
message decoding.
This method also sends feedback messages to the server every
*keepalive_interval* (in seconds). The value of this parameter must
be set to at least 1 second, but it can have a fractional part.
If the *keepalive_interval* is not specified, the value of
*status_interval* specified in the `start_replication()` or
`start_replication_expert()` will be used.
The client must confirm every processed message by calling
`send_feedback()` method on the corresponding replication cursor. A
reference to the cursor is provided in the `ReplicationMessage` as an
attribute.
The following example is a sketch implementation of ``consume()``
callable for logical replication::
class LogicalStreamConsumer(object):
# ...
def __call__(self, msg):
self.process_message(msg.payload)
msg.cursor.send_feedback(flush_lsn=msg.data_start)
consumer = LogicalStreamConsumer()
cur.consume_stream(consumer)
.. warning::
When using replication with slots, failure to constantly consume
*and* report success to the server appropriately can eventually
lead to "disk full" condition on the server, because the server
retains all the WAL segments that might be needed to stream the
changes via all of the currently open replication slots.
.. versionchanged:: 2.8.3
changed the default value of the *keepalive_interval* parameter to `!None`.
.. method:: send_feedback(write_lsn=0, flush_lsn=0, apply_lsn=0, reply=False, force=False)
:param write_lsn: a LSN position up to which the client has written the data locally
:param flush_lsn: a LSN position up to which the client has processed the
data reliably (the server is allowed to discard all
and every data that predates this LSN)
:param apply_lsn: a LSN position up to which the warm standby server
has applied the changes (physical replication
master-slave protocol only)
:param reply: request the server to send back a keepalive message immediately
:param force: force sending a feedback message regardless of status_interval timeout
Use this method to report to the server that all messages up to a
certain LSN position have been processed on the client and may be
discarded on the server.
If the *reply* or *force* parameters are not set, this method will
just update internal structures without sending the feedback message
to the server. The library sends feedback message automatically
when *status_interval* timeout is reached. For this to work, you must
call `send_feedback()` on the same Cursor that you called `start_replication()`
on (the one in `message.cursor`) or your feedback will be lost.
.. versionchanged:: 2.8.3
added the *force* parameter.
Low-level replication cursor methods for :ref:`asynchronous connection
<async-support>` operation.
With the synchronous connection a call to `consume_stream()` handles all
the complexity of handling the incoming messages and sending keepalive
replies, but at times it might be beneficial to use low-level interface
for better control, in particular to `~select` on multiple sockets. The
following methods are provided for asynchronous operation:
.. method:: read_message()
Try to read the next message from the server without blocking and
return an instance of `ReplicationMessage` or `!None`, in case there
are no more data messages from the server at the moment.
This method should be used in a loop with asynchronous connections
(after calling `start_replication()` once). For synchronous
connections see `consume_stream()`.
The returned message's `~ReplicationMessage.payload` is an instance of
`!unicode` decoded according to connection `~connection.encoding`
*iff* *decode* was set to `!True` in the initial call to
`start_replication()` on this connection, otherwise it is an instance
of `!bytes` with no decoding.
It is expected that the calling code will call this method repeatedly
in order to consume all of the messages that might have been buffered
until `!None` is returned. After receiving `!None` from this method
the caller should use `~select.select()` or `~select.poll()` on the
corresponding connection to block the process until there is more data
from the server.
Last, but not least, this method sends feedback messages when
*status_interval* timeout is reached or when keepalive message with
reply request arrived from the server.
.. method:: fileno()
Call the corresponding connection's `~connection.fileno()` method and
return the result.
This is a convenience method which allows replication cursor to be
used directly in `~select.select()` or `~select.poll()` calls.
.. attribute:: io_timestamp
A `~datetime` object representing the timestamp at the moment of last
communication with the server (a data or keepalive message in either
direction).
.. attribute:: feedback_timestamp
A `~datetime` object representing the timestamp at the moment when
the last feedback message sent to the server.
.. versionadded:: 2.8.3
.. attribute:: wal_end
LSN position of the current end of WAL on the server at the
moment of last data or keepalive message received from the
server.
.. versionadded:: 2.8
An actual example of asynchronous operation might look like this::
from select import select
from datetime import datetime
def consume(msg):
# ...
msg.cursor.send_feedback(flush_lsn=msg.data_start)
status_interval = 10.0
while True:
msg = cur.read_message()
if msg:
consume(msg)
else:
now = datetime.now()
timeout = status_interval - (now - cur.feedback_timestamp).total_seconds()
try:
sel = select([cur], [], [], max(0, timeout))
except InterruptedError:
pass # recalculate timeout and continue
.. index::
pair: Cursor; Replication
.. autoclass:: StopReplication
.. index:: .. index::
single: Data types; Additional single: Data types; Additional
@ -562,26 +160,25 @@ JSON_ adaptation
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
.. versionadded:: 2.5 .. 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 |jsons|_ Psycopg can adapt Python objects to and from the PostgreSQL |pgjson|_ type.
types. With PostgreSQL 9.2 and following versions adaptation is With PostgreSQL 9.2 adaptation is available out-of-the-box. To use JSON data
available out-of-the-box. To use JSON data with previous database versions with previous database versions (either with the `9.1 json extension`__, but
(either with the `9.1 json extension`__, but even if you want to convert text even if you want to convert text fields to JSON) you can use
fields to JSON) you can use the `register_json()` function. `register_json()`.
.. __: http://people.planetpostgresql.org/andrew/index.php?/archives/255-JSON-for-PG-9.2-...-and-now-for-9.1!.html .. __: http://people.planetpostgresql.org/andrew/index.php?/archives/255-JSON-for-PG-9.2-...-and-now-for-9.1!.html
The Python :py:mod:`json` module is used by default to convert Python objects The Python library used to convert Python objects to JSON depends on the
to JSON and to parse data from the database. 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: https://www.json.org/ .. _JSON: http://www.json.org/
.. |json| replace:: :sql:`json` .. |pgjson| replace:: :sql:`json`
.. |jsonb| replace:: :sql:`jsonb` .. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html
.. |jsons| replace:: |json| and |jsonb| .. _simplejson: http://pypi.python.org/pypi/simplejson/
.. _jsons: https://www.postgresql.org/docs/current/static/datatype-json.html
In order to pass a Python object to the database as query argument you can use In order to pass a Python object to the database as query argument you can use
the `Json` adapter:: the `Json` adapter::
@ -589,22 +186,8 @@ the `Json` adapter::
curs.execute("insert into mytable (jsondata) values (%s)", curs.execute("insert into mytable (jsondata) values (%s)",
[Json({'a': 100})]) [Json({'a': 100})])
Reading from the database, |json| and |jsonb| values will be automatically Reading from the database, |pgjson| values will be automatically converted to
converted to Python objects. Python objects.
.. note::
If you are using the PostgreSQL :sql:`json` data type but you want to read
it as string in Python instead of having it parsed, your can either cast
the column to :sql:`text` in the query (it is an efficient operation, that
doesn't involve a copy)::
cur.execute("select jsondata::text from mytable")
or you can register a no-op `!loads()` function with
`register_default_json()`::
psycopg2.extras.register_default_json(loads=lambda x: x)
.. note:: .. note::
@ -621,7 +204,7 @@ converted to Python objects.
effects. effects.
If you want to customize the adaptation from Python to PostgreSQL you can If you want to customize the adaptation from Python to PostgreSQL you can
either provide a custom `!dumps()` function to `Json`:: either provide a custom `!dumps()` function to `!Json`::
curs.execute("insert into mytable (jsondata) values (%s)", curs.execute("insert into mytable (jsondata) values (%s)",
[Json({'a': 100}, dumps=simplejson.dumps)]) [Json({'a': 100}, dumps=simplejson.dumps)])
@ -636,22 +219,13 @@ or you can subclass it overriding the `~Json.dumps()` method::
[MyJson({'a': 100})]) [MyJson({'a': 100})])
Customizing the conversion from PostgreSQL to Python can be done passing a Customizing the conversion from PostgreSQL to Python can be done passing a
custom `!loads()` function to `register_json()`. For the builtin data types custom `!loads()` function to `register_json()` (or `register_default_json()`
(|json| from PostgreSQL 9.2, |jsonb| from PostgreSQL 9.4) use for PostgreSQL 9.2). For example, if you want to convert the float values
`register_default_json()` and `register_default_jsonb()`. For example, if you from :sql:`json` into :py:class:`~decimal.Decimal` you can use::
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) loads = lambda x: json.loads(x, parse_float=Decimal)
psycopg2.extras.register_json(conn, loads=loads) psycopg2.extras.register_json(conn, loads=loads)
Or, if you want to use an alternative JSON module implementation, such as the
faster UltraJSON_, you can use::
psycopg2.extras.register_default_json(loads=ujson.loads, globally=True)
psycopg2.extras.register_default_jsonb(loads=ujson.loads, globally=True)
.. _UltraJSON: https://pypi.org/project/ujson/
.. autoclass:: Json .. autoclass:: Json
@ -660,15 +234,8 @@ faster UltraJSON_, you can use::
.. autofunction:: register_json .. autofunction:: register_json
.. versionchanged:: 2.5.4
added the *name* parameter to enable :sql:`jsonb` support.
.. autofunction:: register_default_json .. autofunction:: register_default_json
.. autofunction:: register_default_jsonb
.. versionadded:: 2.5.4
.. index:: .. index::
@ -707,7 +274,7 @@ can be enabled using the `register_hstore()` function.
.. |hstore| replace:: :sql:`hstore` .. |hstore| replace:: :sql:`hstore`
.. _hstore: https://www.postgresql.org/docs/current/static/hstore.html .. _hstore: http://www.postgresql.org/docs/current/static/hstore.html
@ -729,7 +296,7 @@ after a table row type) into a Python named tuple, or into a regular tuple if
:py:func:`collections.namedtuple` is not found. :py:func:`collections.namedtuple` is not found.
.. |CREATE TYPE| replace:: :sql:`CREATE TYPE` .. |CREATE TYPE| replace:: :sql:`CREATE TYPE`
.. _CREATE TYPE: https://www.postgresql.org/docs/current/static/sql-createtype.html .. _CREATE TYPE: http://www.postgresql.org/docs/current/static/sql-createtype.html
.. doctest:: .. doctest::
@ -843,7 +410,7 @@ PostgreSQL |range|_ types. Builtin |range| types are supported out-of-the-box;
user-defined |range| types can be adapted using `register_range()`. user-defined |range| types can be adapted using `register_range()`.
.. |range| replace:: :sql:`range` .. |range| replace:: :sql:`range`
.. _range: https://www.postgresql.org/docs/current/static/rangetypes.html .. _range: http://www.postgresql.org/docs/current/static/rangetypes.html
.. autoclass:: Range .. autoclass:: Range
@ -852,20 +419,12 @@ user-defined |range| types can be adapted using `register_range()`.
features: it doesn't perform normalization and doesn't implement all the features: it doesn't perform normalization and doesn't implement all the
operators__ supported by the database. operators__ supported by the database.
.. __: https://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE .. __: http://www.postgresql.org/docs/current/static/functions-range.html#RANGE-OPERATORS-TABLE
`!Range` objects are immutable, hashable, and support the ``in`` operator `!Range` objects are immutable, hashable, and support the ``in`` operator
(checking if an element is within the range). They can be tested for (checking if an element is within the range). They can be tested for
equivalence. Empty ranges evaluate to `!False` in boolean context, equivalence but not for ordering. Empty ranges evaluate to `!False` in
nonempty evaluate to `!True`. boolean context, nonempty evaluate to `!True`.
.. versionchanged:: 2.5.3
`!Range` objects can be sorted although, as on the server-side, this
ordering is not particularly meangingful. It is only meant to be used
by programs assuming objects using `!Range` as primary key can be
sorted on them. In previous versions comparing `!Range`\s raises
`!TypeError`.
Although it is possible to instantiate `!Range` objects, the class doesn't Although it is possible to instantiate `!Range` objects, the class doesn't
have an adapter registered, so you cannot normally pass these instances as have an adapter registered, so you cannot normally pass these instances as
@ -894,17 +453,6 @@ automatically casted into instances of these classes.
.. autoclass:: DateTimeRange .. autoclass:: DateTimeRange
.. autoclass:: DateTimeTZRange .. autoclass:: DateTimeTZRange
.. note::
Python lacks a representation for :sql:`infinity` date so Psycopg converts
the value to `date.max` and such. When written into the database these
dates will assume their literal value (e.g. :sql:`9999-12-31` instead of
:sql:`infinity`). Check :ref:`infinite-dates-handling` for an example of
an alternative adapter to map `date.max` to :sql:`infinity`. An
alternative dates adapter will be used automatically by the `DateRange`
adapter and so on.
Custom |range| types (created with |CREATE TYPE|_ :sql:`... AS RANGE`) can be Custom |range| types (created with |CREATE TYPE|_ :sql:`... AS RANGE`) can be
adapted to a custom `Range` subclass: adapted to a custom `Range` subclass:
@ -968,29 +516,12 @@ UUID data type
.. index:: .. index::
pair: INET; Data types pair: INET; Data types
pair: CIDR; Data types
pair: MACADDR; Data types
.. _adapt-network: :sql:`inet` data type
^^^^^^^^^^^^^^^^^^^^^^
Networking data types .. versionadded:: 2.0.9
^^^^^^^^^^^^^^^^^^^^^ .. versionchanged:: 2.4.5 added inet array support.
By default Psycopg casts the PostgreSQL networking data types (:sql:`inet`,
:sql:`cidr`, :sql:`macaddr`) into ordinary strings; array of such types are
converted into lists of strings.
.. versionchanged:: 2.7
in previous version array of networking types were not treated as arrays.
.. autofunction:: register_ipaddress
.. autofunction:: register_inet
.. deprecated:: 2.7
this function will not receive further development and may disappear in
future versions.
.. doctest:: .. doctest::
@ -1005,78 +536,24 @@ converted into lists of strings.
'192.168.0.1/24' '192.168.0.1/24'
.. autofunction:: register_inet
.. autoclass:: Inet .. autoclass:: Inet
.. deprecated:: 2.7
this object will not receive further development and may disappear in
future versions.
.. index::
single: Time zones; Fractional
.. _fast-exec: Fractional time zones
---------------------
Fast execution helpers .. autofunction:: register_tstz_w_secs
----------------------
The current implementation of `~cursor.executemany()` is (using an extremely .. versionadded:: 2.0.9
charitable understatement) not particularly performing. These functions can
be used to speed up the repeated execution of a statement against a set of
parameters. By reducing the number of server roundtrips the performance can be
`orders of magnitude better`__ than using `!executemany()`.
.. __: https://github.com/psycopg/psycopg2/issues/491#issuecomment-276551038
.. autofunction:: execute_batch
.. code:: python
>>> nums = ((1,), (5,), (10,))
>>> execute_batch(cur, "INSERT INTO test (num) VALUES (%s)", nums)
>>> tuples = ((123, "foo"), (42, "bar"), (23, "baz"))
>>> execute_batch(cur, "INSERT INTO test (num, data) VALUES (%s, %s)", tuples)
.. versionadded:: 2.7
.. note::
`!execute_batch()` can be also used in conjunction with PostgreSQL
prepared statements using |PREPARE|_, |EXECUTE|_, |DEALLOCATE|_.
Instead of executing::
execute_batch(cur,
"big and complex SQL with %s %s params",
params_list)
it is possible to execute something like::
cur.execute("PREPARE stmt AS big and complex SQL with $1 $2 params")
execute_batch(cur, "EXECUTE stmt (%s, %s)", params_list)
cur.execute("DEALLOCATE stmt")
which may bring further performance benefits: if the operation to perform
is complex, every single execution will be faster as the query plan is
already cached; furthermore the amount of data to send on the server will
be lesser (one |EXECUTE| per param set instead of the whole, likely
longer, statement).
.. |PREPARE| replace:: :sql:`PREPARE`
.. _PREPARE: https://www.postgresql.org/docs/current/static/sql-prepare.html
.. |EXECUTE| replace:: :sql:`EXECUTE`
.. _EXECUTE: https://www.postgresql.org/docs/current/static/sql-execute.html
.. |DEALLOCATE| replace:: :sql:`DEALLOCATE`
.. _DEALLOCATE: https://www.postgresql.org/docs/current/static/sql-deallocate.html
.. autofunction:: execute_values
.. versionadded:: 2.7
.. versionchanged:: 2.8
added the *fetch* parameter.
.. versionchanged:: 2.2.2
function is no-op: see :ref:`tz-handling`.
.. index:: .. index::
pair: Example; Coroutine; pair: Example; Coroutine;
@ -1088,6 +565,3 @@ Coroutine support
.. autofunction:: wait_select(conn) .. 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

@ -7,30 +7,6 @@ Here are a few gotchas you may encounter using `psycopg2`. Feel free to
suggest new entries! suggest new entries!
Meta
----
.. _faq-question:
.. cssclass:: faq
How do I ask a question?
- Have you first checked if your question is answered already in the
documentation?
- If your question is about installing psycopg, have you checked the
:ref:`install FAQ <faq-compile>` and the :ref:`install docs
<installation>`?
- Have you googled for your error message?
- If you haven't found an answer yet, please write to the `Mailing List`_.
- If you haven't found a bug, DO NOT write to the bug tracker to ask
questions. You will only get piro grumpy.
.. _mailing list: https://www.postgresql.org/list/psycopg/
.. _faq-transactions: .. _faq-transactions:
Problems with transactions handling Problems with transactions handling
@ -64,7 +40,7 @@ I receive the error *current transaction is aborted, commands ignored until end
PostgreSQL supports nested transactions using the |SAVEPOINT|_ command). PostgreSQL supports nested transactions using the |SAVEPOINT|_ command).
.. |SAVEPOINT| replace:: :sql:`SAVEPOINT` .. |SAVEPOINT| replace:: :sql:`SAVEPOINT`
.. _SAVEPOINT: https://www.postgresql.org/docs/current/static/sql-savepoint.html .. _SAVEPOINT: http://www.postgresql.org/docs/current/static/sql-savepoint.html
.. _faq-transaction-aborted-multiprocess: .. _faq-transaction-aborted-multiprocess:
@ -97,7 +73,7 @@ Why does `!cursor.execute()` raise the exception *can't adapt*?
I can't pass an integer or a float parameter to my query: it says *a number is required*, but *it is* a number! I can't pass an integer or a float parameter to my query: it says *a number is required*, but *it is* a number!
In your query string, you always have to use ``%s`` placeholders, In your query string, you always have to use ``%s`` placeholders,
even when passing a number. All Python objects are converted by Psycopg event when passing a number. All Python objects are converted by Psycopg
in their SQL representation, so they get passed to the query as strings. in their SQL representation, so they get passed to the query as strings.
See :ref:`query-parameters`. :: See :ref:`query-parameters`. ::
@ -132,19 +108,6 @@ My database is Unicode, but I receive all the strings as UTF-8 `!str`. Can I rec
See :ref:`unicode-handling` for the gory details. See :ref:`unicode-handling` for the gory details.
.. _faq-bytes:
.. cssclass:: faq
My database is in mixed encoding. My program was working on Python 2 but Python 3 fails decoding the strings. How do I avoid decoding?
From psycopg 2.8 you can use the following adapters to always return bytes
from strings::
psycopg2.extensions.register_type(psycopg2.extensions.BYTES)
psycopg2.extensions.register_type(psycopg2.extensions.BYTESARRAY)
See :ref:`unicode-handling` for an example.
.. _faq-float: .. _faq-float:
.. cssclass:: faq .. cssclass:: faq
@ -158,54 +121,10 @@ Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!D
psycopg2.extensions.register_type(DEC2FLOAT) psycopg2.extensions.register_type(DEC2FLOAT)
See :ref:`type-casting-from-sql-to-python` to read the relevant See :ref:`type-casting-from-sql-to-python` to read the relevant
documentation. If you find `!psycopg2.extensions.DECIMAL` not available, use documentation. If you find `!psycopg2.extensions.DECIMAL` not avalable, use
`!psycopg2._psycopg.DECIMAL` instead. `!psycopg2._psycopg.DECIMAL` instead.
.. _faq-json-adapt:
.. cssclass:: faq
Psycopg automatically converts PostgreSQL :sql:`json` data into Python objects. How can I receive strings instead?
The easiest way to avoid JSON parsing is to register a no-op function with
`~psycopg2.extras.register_default_json()`::
psycopg2.extras.register_default_json(loads=lambda x: x)
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 supposed 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-identifier:
.. cssclass:: faq
How can I pass field/table names to a query?
The arguments in the `~cursor.execute()` methods can only represent data
to pass to the query: they cannot represent a table or field name::
# This doesn't work
cur.execute("insert into %s values (%s)", ["my_table", 42])
If you want to build a query dynamically you can use the objects exposed
by the `psycopg2.sql` module::
cur.execute(
sql.SQL("insert into %s values (%%s)") % [sql.Identifier("my_table")],
[42])
.. _faq-bytea-9.0: .. _faq-bytea-9.0:
.. cssclass:: faq .. cssclass:: faq
@ -221,8 +140,8 @@ Transferring binary data from PostgreSQL 9.0 doesn't work.
session before reading binary data; session before reading binary data;
- upgrade the libpq library on the client to at least 9.0. - upgrade the libpq library on the client to at least 9.0.
.. __: https://www.postgresql.org/docs/current/static/datatype-binary.html .. __: http://www.postgresql.org/docs/current/static/datatype-binary.html
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
.. _faq-array: .. _faq-array:
@ -271,64 +190,24 @@ When should I save and re-use a connection as opposed to creating a new one as n
What are the advantages or disadvantages of using named cursors? What are the advantages or disadvantages of using named cursors?
The only disadvantages is that they use up resources on the server and The only disadvantages is that they use up resources on the server and
that there is a little overhead because at least two queries (one to that there is a little overhead because a at least two queries (one to
create the cursor and one to fetch the initial result set) are issued to create the cursor and one to fetch the initial result set) are issued to
the backend. The advantage is that data is fetched one chunk at a time: the backend. The advantage is that data is fetched one chunk at a time:
using small `~cursor.fetchmany()` values it is possible to use very using small `~cursor.fetchmany()` values it is possible to use very
little memory on the client and to skip or discard parts of the result set. 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`__.
.. __: https://www.psycopg.org/articles/2014/07/20/cancelling-postgresql-statements-python/
.. code-block:: pycon
>>> psycopg2.extensions.set_wait_callback(psycopg2.extras.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: .. _faq-compile:
Problems compiling and installing psycopg2 Problems compiling and deploying psycopg2
------------------------------------------ -----------------------------------------
.. _faq-wheels:
.. cssclass:: faq
Psycopg 2.8 fails to install, Psycopg 2.7 was working fine.
With Psycopg 2.7 you were installing binary packages, but they have proven
unreliable so now you have to install them explicitly using the
``psycopg2-binary`` package. See :ref:`binary-packages` for all the
details.
.. _faq-python-h: .. _faq-python-h:
.. cssclass:: faq .. cssclass:: faq
I can't compile `!psycopg2`: the compiler says *error: Python.h: No such file or directory*. What am I missing? I can't compile `!psycopg2`: the compiler says *error: Python.h: No such file or directory*. What am I missing?
You need to install a Python development package: it is usually called You need to install a Python development package: it is usually called
``python-dev`` or ``python3-dev`` according to your Python version. ``python-dev``.
.. _faq-libpq-fe-h: .. _faq-libpq-fe-h:
@ -343,28 +222,19 @@ I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file
.. cssclass:: faq .. cssclass:: faq
`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported. `!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported.
This means that Psycopg was compiled with |lo_truncate|_ support (*i.e.* This means that Psycopg has been compiled with |lo_truncate|_ support,
the libpq used at compile time was version >= 8.3) but at runtime an older which means that the libpq used at compile time was version >= 8.3, but at
libpq dynamic library is found. runtime an older libpq library is found. You can use::
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:
.. code-block:: shell
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq $ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq
to find what is the libpq dynamic library used at runtime. to find what is the version used at runtime.
You can avoid the problem by using the same version of the You can avoid the problem by using the same version of the
:program:`pg_config` at install time and the libpq at runtime. :program:`pg_config` at install time and the libpq at runtime.
.. |lo_truncate| replace:: `!lo_truncate()` .. |lo_truncate| replace:: `!lo_truncate()`
.. _lo_truncate: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE .. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
.. _faq-import-mod_wsgi: .. _faq-import-mod_wsgi:
@ -378,5 +248,6 @@ Psycopg raises *ImportError: cannot import name tz* on import in mod_wsgi / ASP,
use the WSGIPythonEggs__ directive. use the WSGIPythonEggs__ directive.
.. _egg: http://peak.telecommunity.com/DevCenter/PythonEggs .. _egg: http://peak.telecommunity.com/DevCenter/PythonEggs
.. __: https://stackoverflow.com/questions/2192323/what-is-the-python-egg-cache-python-egg-cache .. __: http://stackoverflow.com/questions/2192323/what-is-the-python-egg-cache-python-egg-cache
.. __: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPythonEggs.html .. __: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPythonEggs

View File

@ -14,19 +14,27 @@ of concurrent :sql:`INSERT`\s or :sql:`UPDATE`\s.
Psycopg 2 is mostly implemented in C as a libpq_ wrapper, resulting in being Psycopg 2 is mostly implemented in C as a libpq_ wrapper, resulting in being
both efficient and secure. It features client-side and :ref:`server-side both efficient and secure. It features client-side and :ref:`server-side
<server-side-cursors>` cursors, :ref:`asynchronous communication <server-side-cursors>` cursors, :ref:`asynchronous communication
<async-support>` and :ref:`notifications <async-notify>`, :ref:`COPY <copy>` <async-support>` and :ref:`notifications <async-notify>`, |COPY-TO-FROM|__
support. Many Python types are supported out-of-the-box and :ref:`adapted to support, and a flexible :ref:`objects adaptation system
matching PostgreSQL data types <python-types-adaptation>`; adaptation can be <python-types-adaptation>`. Many basic Python types are supported
extended and customized thanks to a flexible :ref:`objects adaptation system out-of-the-box and mapped to matching PostgreSQL data types, such as strings
<adapting-new-types>`. (both byte strings and Unicode), numbers (ints, longs, floats, decimals),
booleans and date/time objects (both built-in and `mx.DateTime`_), several
types of :ref:`binary objects <adapt-binary>`. Also available are mappings
between lists and PostgreSQL arrays of any supported type, between
:ref:`dictionaries and PostgreSQL hstore <adapt-hstore>`, between
:ref:`tuples/namedtuples and PostgreSQL composite types <adapt-composite>`,
and between Python objects and :ref:`JSON <adapt-json>`.
Psycopg 2 is both Unicode and Python 3 friendly. Psycopg 2 is both Unicode and Python 3 friendly.
.. _Psycopg: https://psycopg.org/ .. _Psycopg: http://initd.org/psycopg/
.. _PostgreSQL: https://www.postgresql.org/ .. _PostgreSQL: http://www.postgresql.org/
.. _Python: https://www.python.org/ .. _Python: http://www.python.org/
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html .. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
.. |COPY-TO-FROM| replace:: :sql:`COPY TO/COPY FROM`
.. __: http://www.postgresql.org/docs/current/static/sql-copy.html
.. rubric:: Contents .. rubric:: Contents
@ -41,15 +49,12 @@ Psycopg 2 is both Unicode and Python 3 friendly.
cursor cursor
advanced advanced
extensions extensions
extras
errors
sql
tz tz
pool pool
extras
errorcodes errorcodes
faq faq
news news
license
.. ifconfig:: builder != 'text' .. ifconfig:: builder != 'text'
@ -57,7 +62,6 @@ Psycopg 2 is both Unicode and Python 3 friendly.
.. rubric:: Indices and tables .. rubric:: Indices and tables
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex`
* :ref:`search` * :ref:`search`
@ -68,3 +72,4 @@ Psycopg 2 is both Unicode and Python 3 friendly.
**To Do items in the documentation** **To Do items in the documentation**
.. todolist:: .. todolist::

View File

@ -1,6 +1,4 @@
.. _installation: Introduction
Installation
============ ============
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com> .. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
@ -8,161 +6,120 @@ Installation
Psycopg is a PostgreSQL_ adapter for the Python_ programming language. It is a Psycopg is a PostgreSQL_ adapter for the Python_ programming language. It is a
wrapper for the libpq_, the official PostgreSQL client library. wrapper for the libpq_, the official PostgreSQL client library.
.. _PostgreSQL: https://www.postgresql.org/ The `psycopg2` package is the current mature implementation of the adapter: it
.. _Python: https://www.python.org/ is a C extension and as such it is only compatible with CPython_. If you want
to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
there is an experimental `porting of Psycopg for Ctypes`__, but it is not as
.. index:: mature as the C implementation yet.
single: Install; from PyPI
single: Install; wheel
single: Wheel
.. _binary-packages:
Quick Install
-------------
For most operating systems, the quickest way to install Psycopg is using the
wheel_ package available on PyPI_:
.. code-block:: console
$ pip install psycopg2-binary
This will install a pre-compiled binary version of the module which does not
require the build or runtime prerequisites described below. Make sure to use
an up-to-date version of :program:`pip` (you can upgrade it using something
like ``pip install -U pip``).
You may then import the ``psycopg2`` package, as usual:
.. code-block:: python
import psycopg2
# Connect to your postgres DB
conn = psycopg2.connect("dbname=test user=postgres")
# Open a cursor to perform database operations
cur = conn.cursor()
# Execute a query
cur.execute("SELECT * FROM my_data")
# Retrieve query results
records = cur.fetchall()
.. _PyPI: https://pypi.org/project/psycopg2-binary/
.. _wheel: https://pythonwheels.com/
psycopg vs psycopg-binary
^^^^^^^^^^^^^^^^^^^^^^^^^
The ``psycopg2-binary`` package is meant for beginners to start playing
with Python and PostgreSQL without the need to meet the build
requirements.
If you are the maintainer of a published package depending on `!psycopg2`
you shouldn't use ``psycopg2-binary`` as a module dependency. **For
production use you are advised to use the source distribution.**
The binary packages come with their own versions of a few C libraries,
among which ``libpq`` and ``libssl``, which will be used regardless of other
libraries available on the client: upgrading the system libraries will not
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
source if you want to maintain binary upgradeability.
.. warning::
The `!psycopg2` wheel package comes packaged, among the others, with its
own ``libssl`` binary. This may create conflicts with other extension
modules binding with ``libssl`` as well, for instance with the Python
`ssl` module: in some cases, under concurrency, the interaction between
the two libraries may result in a segfault. In case of doubts you are
advised to use a package built from source.
.. index::
single: Install; disable wheel
single: Wheel; disable
.. _disable-wheel:
Change in binary packages between Psycopg 2.7 and 2.8
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In version 2.7.x, :command:`pip install psycopg2` would have tried to install
automatically the binary package of Psycopg. Because of concurrency problems
binary packages have displayed, ``psycopg2-binary`` has become a separate
package, and from 2.8 it has become the only way to install the binary
package.
If you are using Psycopg 2.7 and you want to disable the use of wheel binary
packages, relying on the system libraries available on your client, you
can use the :command:`pip` |--no-binary option|__, e.g.:
.. code-block:: console
$ pip install --no-binary :all: psycopg2
.. |--no-binary option| replace:: ``--no-binary`` option
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
which can be specified in your :file:`requirements.txt` files too, e.g. use:
.. code-block:: none
psycopg2>=2.7,<2.8 --no-binary psycopg2
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
always compile it from source. Of course in this case you will have to meet
the :ref:`build prerequisites <build-prerequisites>`.
.. index::
single: Prerequisites
Prerequisites
-------------
The current `!psycopg2` implementation supports: The current `!psycopg2` implementation supports:
.. - Python 2 versions from 2.5 to 2.7
NOTE: keep consistent with setup.py and the /features/ page. - Python 3 versions from 3.1 to 3.3
- PostgreSQL versions from 7.4 to 9.2
.. _PostgreSQL: http://www.postgresql.org/
.. _Python: http://www.python.org/
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
.. _CPython: http://en.wikipedia.org/wiki/CPython
.. _Ctypes: http://docs.python.org/library/ctypes.html
.. __: https://github.com/mvantellingen/psycopg2-ctypes
- Python versions from 3.8 to 3.13
- PostgreSQL server versions from 7.4 to 17
- PostgreSQL client library version from 9.1
.. note:: .. note::
Not all the psycopg2 versions support all the supported Python versions. `!psycopg2` usually depends at runtime on the libpq dynamic library.
However it can connect to PostgreSQL servers of any supported version,
Please see the :ref:`release notes <news>` to verify when the support for independently of the version of the libpq used: just install the most
a new Python version was added and when the support for an old Python recent libpq version or the most practical, without trying to match it to
version was removed. the version of the PostgreSQL server you will have to connect to.
.. _build-prerequisites: Installation
============
Build prerequisites If possible, and usually it is, please :ref:`install Psycopg from a package
^^^^^^^^^^^^^^^^^^^ <install-from-package>` available for your distribution or operating system.
The build prerequisites are to be met in order to install Psycopg from source Compiling from source is a very easy task, however `!psycopg2` is a C
code, from a source distribution package, GitHub_ or from PyPI. extension module and as such it requires a few more things in place respect to
a pure Python module. So, if you don't have experience compiling Python
extension packages, *above all if you are a Windows or a Mac OS user*, please
use a pre-compiled package and go straight to the :ref:`module usage <usage>`
avoid bothering with the gory details.
.. _GitHub: https://github.com/psycopg/psycopg2
Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install
it from sources you will need: .. _install-from-package:
Install from a package
----------------------
.. index::
pair: Install; Linux
**Linux**
Psycopg is available already packaged in many Linux distributions: look
for a package such as ``python-psycopg2`` using the package manager of
your choice.
On Debian, Ubuntu and other deb-based distributions you should just need::
sudo apt-get install python-psycopg2
to install the package with all its dependencies.
.. index::
pair: Install; Mac OS X
**Mac OS X**
Psycopg is available as a `fink package`__ in the *unstable* tree: you may
install it with::
fink install psycopg2-py27
.. __: http://pdb.finkproject.org/pdb/package.php/psycopg2-py27
The library is also available on `MacPorts`__ try::
sudo port install py27-psycopg2
.. __: http://www.macports.org/
.. index::
pair: Install; Windows
**Microsoft Windows**
Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
installation executable. Download. Double click. Done.
.. __: http://www.stickpeople.com/projects/python/win-psycopg/
.. index::
single: Install; from source
.. _install-from-source:
Install from source
-------------------
These notes illustrate how to compile Psycopg on Linux. If you want to compile
Psycopg on other platforms you may have to adjust some details accordingly.
.. _requirements:
Psycopg is a C wrapper to the libpq PostgreSQL client library. To install it
from sources you will need:
- A C compiler. - A C compiler.
- The Python header files. They are usually installed in a package such as - The Python header files. They are usually installed in a package such as
**python-dev** or **python3-dev**. A message such as *error: Python.h: No **python-dev**. A message such as *error: Python.h: No such file or
such file or directory* is an indication that the Python headers are directory* is an indication that the Python headers are missing.
missing.
- The libpq header files. They are usually installed in a package such as - The libpq header files. They are usually installed in a package such as
**libpq-dev**. If you get an *error: libpq-fe.h: No such file or directory* **libpq-dev**. If you get an *error: libpq-fe.h: No such file or directory*
@ -174,40 +131,12 @@ it from sources you will need:
running ``pg_config --version``: if it returns an error or an unexpected running ``pg_config --version``: if it returns an error or an unexpected
version number then locate the directory containing the :program:`pg_config` version number then locate the directory containing the :program:`pg_config`
shipped with the right libpq version (usually shipped with the right libpq version (usually
``/usr/lib/postgresql/X.Y/bin/``) and add it to the :envvar:`PATH`: ``/usr/lib/postgresql/X.Y/bin/``) and add it to the :envvar:`PATH`::
.. code-block:: console
$ export PATH=/usr/lib/postgresql/X.Y/bin/:$PATH $ export PATH=/usr/lib/postgresql/X.Y/bin/:$PATH
You only need :program:`pg_config` to compile `!psycopg2`, not for its You only need it to compile and install `!psycopg2`, not for its regular
regular usage. usage.
Once everything is in place it's just a matter of running the standard:
.. code-block:: console
$ pip install psycopg2
or, from the directory containing the source code:
.. code-block:: console
$ python setup.py build
$ python setup.py install
Runtime requirements
^^^^^^^^^^^^^^^^^^^^
Unless you compile `!psycopg2` as a static library, or you install it from a
self-contained wheel package, it will need the libpq_ library at runtime
(usually distributed in a ``libpq.so`` or ``libpq.dll`` file). `!psycopg2`
relies on the host OS to find the library if the library is installed in a
standard location there is usually no problem; if the library is in a
non-standard location you will have to tell Psycopg how to find it,
which is OS-dependent (for instance setting a suitable
:envvar:`LD_LIBRARY_PATH` on Linux).
.. note:: .. note::
@ -217,40 +146,61 @@ which is OS-dependent (for instance setting a suitable
:program:`ldd`) if the module ``psycopg2/_psycopg.so`` is linked to the :program:`ldd`) if the module ``psycopg2/_psycopg.so`` is linked to the
right ``libpq.so``. right ``libpq.so``.
.. note::
Whatever version of libpq `!psycopg2` is compiled with, it will be
possible to connect to PostgreSQL servers of any supported version: just .. index::
install the most recent libpq version or the most practical, without single: Install; from PyPI
trying to match it to the version of the PostgreSQL server you will have
to connect to. .. _package-manager:
Use a Python package manager
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If the above requirements are satisfied, you can use :program:`easy_install`,
:program:`pip` or whatever the Python package manager of the week::
$ pip install psycopg2
Please refer to your package manager documentation about performing a local or
global installation, :program:`virtualenv` (fully supported by recent Psycopg
versions), using different Python versions and other nuances.
.. index:: .. index::
single: setup.py single: setup.py
single: setup.cfg single: setup.cfg
Non-standard builds .. _source-package:
-------------------
Use the source package
^^^^^^^^^^^^^^^^^^^^^^
You can download a copy of Psycopg source files from the `Psycopg download
page`__. Once unpackaged, to compile and install the package you can run::
$ python setup.py build
$ sudo python setup.py install
If you have less standard requirements such as: If you have less standard requirements such as:
- creating a :ref:`debug build <debug-build>`, - creating a :ref:`debug build <debug-build>`,
- using :program:`pg_config` not in the :envvar:`PATH`, - using :program:`pg_config` not in the :envvar:`PATH`,
- supporting ``mx.DateTime``,
then take a look at the ``setup.cfg`` file. then take a look at the ``setup.cfg`` file.
Some of the options available in ``setup.cfg`` are also available as command Some of the options available in ``setup.cfg`` are also available as command
line arguments of the ``build_ext`` sub-command. For instance you can specify line arguments of the ``build_ext`` sub-command. For instance you can specify
an alternate :program:`pg_config` location using: an alternate :program:`pg_config` version using::
.. code-block:: console
$ python setup.py build_ext --pg-config /path/to/pg_config build $ python setup.py build_ext --pg-config /path/to/pg_config build
Use ``python setup.py build_ext --help`` to get a list of the options Use ``python setup.py build_ext --help`` to get a list of the options
supported. supported.
.. __: http://initd.org/psycopg/download/
.. index:: .. index::
single: debug single: debug
@ -259,83 +209,30 @@ supported.
.. _debug-build: .. _debug-build:
Creating a debug build Creating a debug build
^^^^^^^^^^^^^^^^^^^^^^ ----------------------
In case of problems, Psycopg can be configured to emit detailed debug In case of problems, Psycopg can be configured to emit detailed debug
messages, which can be very useful for diagnostics and to report a bug. In messages, which can be very useful for diagnostics and to report a bug. In
order to create a debug package: order to create a debug package:
- `Download`__ and unpack the Psycopg *source package* (the ``.tar.gz`` - `Download`__ and unpack the Psycopg source package.
package).
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the - Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
``define`` option. ``define`` option.
- :ref:`Compile and install <build-prerequisites>` the package. - :ref:`Compile and install <source-package>` the package.
- Set the :envvar:`PSYCOPG_DEBUG` environment variable: - Set the :envvar:`PSYCOPG_DEBUG` variable::
.. code-block:: console
$ export PSYCOPG_DEBUG=1 $ export PSYCOPG_DEBUG=1
- Run your program (making sure that the `!psycopg2` package imported is the - 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 one you just compiled and not e.g. the system one): you will have a copious
stream of informations printed on stderr. stream of informations printed on stdout.
.. __: https://pypi.org/project/psycopg2/#files .. __: http://initd.org/psycopg/download/
Non-standard Python Implementation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The `psycopg2` package is the current mature implementation of the adapter: it
is a C extension and as such it is only compatible with CPython_. If you want
to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
there is a couple of alternative:
- a `Ctypes port`__, but it is not as mature as the C implementation yet
and it is not as feature-complete;
- a `CFFI port`__ which is currently more used and reported more efficient on
PyPy, but please be careful of its version numbers because they are not
aligned to the official psycopg2 ones and some features may differ.
.. _PostgreSQL: https://www.postgresql.org/
.. _Python: https://www.python.org/
.. _libpq: https://www.postgresql.org/docs/current/static/libpq.html
.. _CPython: https://en.wikipedia.org/wiki/CPython
.. _Ctypes: https://docs.python.org/library/ctypes.html
.. __: https://github.com/mvantellingen/psycopg2-ctypes
.. __: https://github.com/chtd/psycopg2cffi
.. index::
single: tests
.. _test-suite:
Running the test suite
----------------------
Once `!psycopg2` is installed you can run the test suite to verify it is
working correctly. From the source directory, you can run:
.. code-block:: console
$ python -c "import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
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.
.. _other-problems: .. _other-problems:
@ -344,21 +241,18 @@ If you still have problems
Try the following. *In order:* Try the following. *In order:*
- Read again the :ref:`build-prerequisites`. - Read again the :ref:`requirements <requirements>`.
- Read the :ref:`FAQ <faq-compile>`. - Read the :ref:`FAQ <faq-compile>`.
- Google for `!psycopg2` *your error message*. Especially useful the week - Google for `!psycopg2` *your error message*. Especially useful the week
after the release of a new OS X version. after the release of a new OS X version.
- Write to the `Mailing List`_. - Write to the `Mailing List`__.
- If you think that you have discovered a bug, test failure or missing feature
please raise a ticket in the `bug tracker`_.
- Complain on your blog or on Twitter that `!psycopg2` is the worst package - Complain on your blog or on Twitter that `!psycopg2` is the worst package
ever and about the quality time you have wasted figuring out the correct ever and about the quality time you have wasted figuring out the correct
:envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you. :envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you.
.. _mailing list: https://www.postgresql.org/list/psycopg/ .. __: http://mail.postgresql.org/mj/mj_wwwusr/domain=postgresql.org?func=lists-long-full&extra=psycopg
.. _bug tracker: https://github.com/psycopg/psycopg2/issues

View File

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

View File

@ -17,44 +17,47 @@ The module interface respects the standard defined in the |DBAPI|_.
single: DSN (Database Source Name) single: DSN (Database Source Name)
.. function:: .. function::
connect(dsn=None, connection_factory=None, cursor_factory=None, async=False, \*\*kwargs) connect(dsn, connection_factory=None, cursor_factory=None, async=False)
connect(\*\*kwargs, connection_factory=None, cursor_factory=None, async=False)
Create a new database session and return a new `connection` object. Create a new database session and return a new `connection` object.
The connection parameters can be specified as a `libpq connection The connection parameters can be specified either as a `libpq connection
string`__ using the *dsn* parameter:: string`__ using the *dsn* parameter::
conn = psycopg2.connect("dbname=test user=postgres password=secret") conn = psycopg2.connect("dbname=test user=postgres password=secret")
or using a set of keyword arguments:: or using a set of keyword arguments::
conn = psycopg2.connect(dbname="test", user="postgres", password="secret") conn = psycopg2.connect(database="test", user="postgres", password="secret")
or using a mix of both: if the same parameter name is specified in both The two call styles are mutually exclusive: you cannot specify connection
sources, the *kwargs* value will have precedence over the *dsn* value. parameters as keyword arguments together with a connection string; only
Note that either the *dsn* or at least one connection-related keyword the parameters not needed for the database connection (*i.e.*
argument is required. *connection_factory*, *cursor_factory*, and *async*) are supported
together with the *dsn* argument.
The basic connection parameters are: The basic connection parameters are:
- `!dbname` -- the database name (`!database` is a deprecated alias) - `!dbname` -- the database name (only in the *dsn* string)
- `!database` -- the database name (only as keyword argument)
- `!user` -- user name used to authenticate - `!user` -- user name used to authenticate
- `!password` -- password used to authenticate - `!password` -- password used to authenticate
- `!host` -- database host address (defaults to UNIX socket if not provided) - `!host` -- database host address (defaults to UNIX socket if not provided)
- `!port` -- connection port number (defaults to 5432 if not provided) - `!port` -- connection port number (defaults to 5432 if not provided)
Any other connection parameter supported by the client library/server can Any other connection parameter supported by the client library/server can
be passed either in the connection string or as a keyword. The PostgreSQL be passed either in the connection string or as keywords. The PostgreSQL
documentation contains the complete list of the `supported parameters`__. documentation contains the complete list of the `supported parameters`__.
Also note that the same parameters can be passed to the client library Also note that the same parameters can be passed to the client library
using `environment variables`__. using `environment variables`__.
.. __: .. __:
.. _connstring: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING .. _connstring: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
.. __: .. __:
.. _connparams: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS .. _connparams: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS
.. __: .. __:
.. _connenvvars: https://www.postgresql.org/docs/current/static/libpq-envars.html .. _connenvvars: http://www.postgresql.org/docs/current/static/libpq-envars.html
Using the *connection_factory* parameter a different class or Using the *connection_factory* parameter a different class or
connections factory can be specified. It should be a callable object connections factory can be specified. It should be a callable object
@ -64,8 +67,7 @@ The module interface respects the standard defined in the |DBAPI|_.
cursors you can use this parameter instead of subclassing a connection. cursors you can use this parameter instead of subclassing a connection.
Using *async*\=\ `!True` an asynchronous connection will be created: see Using *async*\=\ `!True` an asynchronous connection will be created: see
:ref:`async-support` to know about advantages and limitations. *async_* is :ref:`async-support` to know about advantages and limitations.
a valid alias for the Python version where ``async`` is a keyword.
.. versionchanged:: 2.4.3 .. versionchanged:: 2.4.3
any keyword argument is passed to the connection. Previously only the any keyword argument is passed to the connection. Previously only the
@ -74,15 +76,8 @@ The module interface respects the standard defined in the |DBAPI|_.
.. versionchanged:: 2.5 .. versionchanged:: 2.5
added the *cursor_factory* parameter. added the *cursor_factory* parameter.
.. versionchanged:: 2.7
both *dsn* and keyword arguments can be specified.
.. versionchanged:: 2.7
added *async_* alias.
.. seealso:: .. seealso::
- `~psycopg2.extensions.parse_dsn`
- libpq `connection string syntax`__ - libpq `connection string syntax`__
- libpq supported `connection parameters`__ - libpq supported `connection parameters`__
- libpq supported `environment variables`__ - libpq supported `environment variables`__
@ -93,8 +88,9 @@ The module interface respects the standard defined in the |DBAPI|_.
.. extension:: .. extension::
The non-connection-related keyword parameters are Psycopg extensions The parameters *connection_factory* and *async* are Psycopg extensions
to the |DBAPI|_. to the |DBAPI|.
.. data:: apilevel .. data:: apilevel
@ -113,14 +109,6 @@ The module interface respects the standard defined in the |DBAPI|_.
by the interface. For `psycopg2` is ``pyformat``. See also by the interface. For `psycopg2` is ``pyformat``. See also
:ref:`query-parameters`. :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
`~psycopg2.extensions.ConnectionInfo.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::
@ -137,15 +125,14 @@ available through the following exceptions:
.. exception:: Warning .. exception:: Warning
Exception raised for important warnings like data truncations while Exception raised for important warnings like data truncations while
inserting, etc. It is a subclass of the Python `StandardError` inserting, etc. It is a subclass of the Python `~exceptions.StandardError`.
(`Exception` on Python 3).
.. exception:: Error .. exception:: Error
Exception that is the base class of all other error exceptions. You can 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 use this to catch all errors with one single `!except` statement. Warnings
are not considered errors and thus not use this class as base. It are not considered errors and thus not use this class as base. It
is a subclass of the Python `StandardError` (`Exception` on Python 3). is a subclass of the Python `!StandardError`.
.. attribute:: pgerror .. attribute:: pgerror
@ -163,16 +150,15 @@ available through the following exceptions:
>>> try: >>> try:
... cur.execute("SELECT * FROM barf") ... cur.execute("SELECT * FROM barf")
... except psycopg2.Error as e: ... except Exception, e:
... pass ... pass
>>> e.pgcode >>> e.pgcode
'42P01' '42P01'
>>> print(e.pgerror) >>> print e.pgerror
ERROR: relation "barf" does not exist ERROR: relation "barf" does not exist
LINE 1: SELECT * FROM barf LINE 1: SELECT * FROM barf
^ ^
.. attribute:: cursor .. attribute:: cursor
The cursor the exception was raised from; `None` if not applicable. The cursor the exception was raised from; `None` if not applicable.
@ -184,7 +170,7 @@ available through the following exceptions:
>>> try: >>> try:
... cur.execute("SELECT * FROM barf") ... cur.execute("SELECT * FROM barf")
... except psycopg2.Error as e: ... except Exception, e:
... pass ... pass
>>> e.diag.severity >>> e.diag.severity
@ -252,14 +238,13 @@ available through the following exceptions:
.. extension:: .. extension::
Psycopg actually raises a different exception for each :sql:`SQLSTATE` Psycopg may raise a few other, more specialized, exceptions: currently
error returned by the database: the classes are available in the `~psycopg2.extensions.QueryCanceledError` and
`psycopg2.errors` module. Every exception class is a subclass of one of `~psycopg2.extensions.TransactionRollbackError` are defined. These
the exception classes defined here though, so they don't need to be exceptions are not exposed by the main `!psycopg2` module but are
trapped specifically: trapping `!Error` or `!DatabaseError` is usually made available by the `~psycopg2.extensions` module. All the
what needed to write a generic error handler; trapping a specific error additional exceptions are subclasses of standard |DBAPI| exceptions, so
such as `!NotNullViolation` can be useful to write specific exception trapping them specifically is not required.
handlers.
This is the exception inheritance layout: This is the exception inheritance layout:
@ -273,6 +258,8 @@ This is the exception inheritance layout:
\|__ `DatabaseError` \|__ `DatabaseError`
\|__ `DataError` \|__ `DataError`
\|__ `OperationalError` \|__ `OperationalError`
\| \|__ `psycopg2.extensions.QueryCanceledError`
\| \|__ `psycopg2.extensions.TransactionRollbackError`
\|__ `IntegrityError` \|__ `IntegrityError`
\|__ `InternalError` \|__ `InternalError`
\|__ `ProgrammingError` \|__ `ProgrammingError`

View File

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

View File

@ -24,18 +24,13 @@ directly in the client application.
.. method:: getconn(key=None) .. method:: getconn(key=None)
Get a free connection from the pool. Get a free connection and assign it to *key* if not `!None`.
The *key* parameter is optional: if used, the connection will be
associated to the key and calling `!getconn()` with the same key again
will return the same connection.
.. method:: putconn(conn, key=None, close=False) .. method:: putconn(conn, key=None, close=False)
Put away a connection. Put away a connection.
If *close* is `!True`, discard the connection from the pool. If *close* is `!True`, discard the connection from the pool.
*key* should be used consistently with `getconn()`.
.. method:: closeall .. method:: closeall
@ -58,3 +53,12 @@ be used.
.. autoclass:: ThreadedConnectionPool .. autoclass:: ThreadedConnectionPool
.. note:: This pool class can be safely used in multi-threaded applications. .. note:: This pool class can be safely used in multi-threaded applications.
.. autoclass:: PersistentConnectionPool
.. note::
This pool class is mostly designed to interact with Zope and probably
not useful in generic applications.

View File

@ -1,147 +0,0 @@
`psycopg2.sql` -- SQL string composition
========================================
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
.. module:: psycopg2.sql
.. versionadded:: 2.7
The module contains objects and functions useful to generate SQL dynamically,
in a convenient and safe way. SQL identifiers (e.g. names of tables and
fields) cannot be passed to the `~cursor.execute()` method like query
arguments::
# This will not work
table_name = 'my_table'
cur.execute("insert into %s values (%s, %s)", [table_name, 10, 20])
The SQL query should be composed before the arguments are merged, for
instance::
# This works, but it is not optimal
table_name = 'my_table'
cur.execute(
"insert into %s values (%%s, %%s)" % table_name,
[10, 20])
This sort of works, but it is an accident waiting to happen: the table name
may be an invalid SQL literal and need quoting; even more serious is the
security problem in case the table name comes from an untrusted source. The
name should be escaped using `~psycopg2.extensions.quote_ident()`::
# This works, but it is not optimal
table_name = 'my_table'
cur.execute(
"insert into %s values (%%s, %%s)" % ext.quote_ident(table_name, cur),
[10, 20])
This is now safe, but it somewhat ad-hoc. In case, for some reason, it is
necessary to include a value in the query string (as opposite as in a value)
the merging rule is still different (`~psycopg2.extensions.adapt()` should be
used...). It is also still relatively dangerous: if `!quote_ident()` is
forgotten somewhere, the program will usually work, but will eventually crash
in the presence of a table or field name with containing characters to escape,
or will present a potentially exploitable weakness.
The objects exposed by the `!psycopg2.sql` module allow generating SQL
statements on the fly, separating clearly the variable parts of the statement
from the query parameters::
from psycopg2 import sql
cur.execute(
sql.SQL("insert into {} values (%s, %s)")
.format(sql.Identifier('my_table')),
[10, 20])
Module usage
------------
Usually you should express the template of your query as an `SQL` instance
with `{}`\-style placeholders and use `~SQL.format()` to merge the variable
parts into them, all of which must be `Composable` subclasses. You can still
have `%s`\ -style placeholders in your query and pass values to
`~cursor.execute()`: such value placeholders will be untouched by
`!format()`::
query = sql.SQL("select {field} from {table} where {pkey} = %s").format(
field=sql.Identifier('my_name'),
table=sql.Identifier('some_table'),
pkey=sql.Identifier('id'))
The resulting object is meant to be passed directly to cursor methods such as
`~cursor.execute()`, `~cursor.executemany()`, `~cursor.copy_expert()`, but can
also be used to compose a query as a Python string, using the
`~Composable.as_string()` method::
cur.execute(query, (42,))
If part of your query is a variable sequence of arguments, such as a
comma-separated list of field names, you can use the `SQL.join()` method to
pass them to the query::
query = sql.SQL("select {fields} from {table}").format(
fields=sql.SQL(',').join([
sql.Identifier('field1'),
sql.Identifier('field2'),
sql.Identifier('field3'),
]),
table=sql.Identifier('some_table'))
`!sql` objects
--------------
The `!sql` objects are in the following inheritance hierarchy:
| `Composable`: the base class exposing the common interface
| ``|__`` `SQL`: a literal snippet of an SQL query
| ``|__`` `Identifier`: a PostgreSQL identifier or dot-separated sequence of identifiers
| ``|__`` `Literal`: a value hardcoded into a query
| ``|__`` `Placeholder`: a `%s`\ -style placeholder whose value will be added later e.g. by `~cursor.execute()`
| ``|__`` `Composed`: a sequence of `!Composable` instances.
.. autoclass:: Composable
.. automethod:: as_string
.. autoclass:: SQL
.. autoattribute:: string
.. automethod:: format
.. automethod:: join
.. autoclass:: Identifier
.. versionchanged:: 2.8
added support for multiple strings.
.. autoattribute:: strings
.. versionadded:: 2.8
previous verions only had a `!string` attribute. The attribute
still exists but is deprecate and will only work if the
`!Identifier` wraps a single string.
.. autoclass:: Literal
.. autoattribute:: wrapped
.. autoclass:: Placeholder
.. autoattribute:: name
.. autoclass:: Composed
.. autoattribute:: seq
.. automethod:: join

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
""" """
extension extension
~~~~~~~~~ ~~~~~~~~~
@ -11,7 +12,7 @@
from docutils import nodes from docutils import nodes
from sphinx.locale import _ from sphinx.locale import _
from docutils.parsers.rst import Directive from sphinx.util.compat import Directive, make_admonition
class extension_node(nodes.Admonition, nodes.Element): pass class extension_node(nodes.Admonition, nodes.Element): pass
@ -28,11 +29,12 @@ class Extension(Directive):
option_spec = {} option_spec = {}
def run(self): def run(self):
node = extension_node('\n'.join(self.content)) nodes = make_admonition(extension_node,
node += nodes.title(_('DB API extension'), _('DB API extension')) self.name, [_('DB API extension')], self.options,
self.state.nested_parse(self.content, self.content_offset, node) self.content, self.lineno, self.content_offset,
node['classes'].append('dbapi-extension') self.block_text, self.state, self.state_machine)
return [node] nodes[0]['classes'].append('dbapi-extension')
return nodes
def visit_extension_node(self, node): def visit_extension_node(self, node):
@ -48,3 +50,4 @@ def setup(app):
text=(visit_extension_node, depart_extension_node)) text=(visit_extension_node, depart_extension_node))
app.add_directive('extension', Extension) app.add_directive('extension', Extension)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
""" """
sql role sql role
~~~~~~~~ ~~~~~~~~
@ -17,3 +18,4 @@ def sql_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
def setup(app): def setup(app):
roles.register_local_role('sql', sql_role) roles.register_local_role('sql', sql_role)

View File

@ -1,57 +1,39 @@
# -*- coding: utf-8 -*-
""" """
ticket role ticket role
~~~~~~~~~~~ ~~~~~~~~~~~
An interpreted text role to link docs to tickets issues. An interpreted text role to link docs to lighthouse issues.
:copyright: Copyright 2013 by Daniele Varrazzo. :copyright: Copyright 2013 by Daniele Varrazzo.
""" """
import re
from docutils import nodes, utils from docutils import nodes, utils
from docutils.parsers.rst import roles from docutils.parsers.rst import roles
def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]): def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
cfg = inliner.document.settings.env.app.config try:
if cfg.ticket_url is None: 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:
msg = inliner.reporter.warning( msg = inliner.reporter.warning(
"ticket not configured: please configure ticket_url in conf.py") "ticket not configured: please configure ticket_url in conf.py")
prb = inliner.problematic(rawtext, rawtext, msg) prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg] return [prb], [msg]
rv = [nodes.Text(name + ' ')] url = url_pattern % num
tokens = re.findall(r'(#?\d+)|([^\d#]+)', text) roles.set_classes(options)
for ticket, noise in tokens: node = nodes.reference(rawtext, 'ticket ' + utils.unescape(text),
if ticket: refuri=url, **options)
num = int(ticket.replace('#', '')) return [node], []
# 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): def setup(app):
app.add_config_value('ticket_url', None, 'env') 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('ticket', ticket_role)
app.add_role('tickets', ticket_role)

View File

@ -1,57 +0,0 @@
#!/usr/bin/env python
"""Create the docs table of the sqlstate errors.
"""
import re
import sys
from collections import namedtuple
from psycopg2._psycopg import sqlstate_errors
def main():
sqlclasses = {}
clsfile = sys.argv[1]
with open(clsfile) as f:
for l in f:
m = re.match(r'/\* Class (..) - (.+) \*/', l)
if m is not None:
sqlclasses[m.group(1)] = m.group(2)
Line = namedtuple('Line', 'colstate colexc colbase sqlstate')
lines = [Line('SQLSTATE', 'Exception', 'Base exception', None)]
for k in sorted(sqlstate_errors):
exc = sqlstate_errors[k]
lines.append(Line(
f"``{k}``", f"`!{exc.__name__}`",
f"`!{get_base_exception(exc).__name__}`", k))
widths = [max(len(l[c]) for l in lines) for c in range(3)]
h = Line(*(['=' * w for w in widths] + [None]))
lines.insert(0, h)
lines.insert(2, h)
lines.append(h)
h1 = '-' * (sum(widths) + len(widths) - 1)
sqlclass = None
for l in lines:
cls = l.sqlstate[:2] if l.sqlstate else None
if cls and cls != sqlclass:
print(f"**Class {cls}**: {sqlclasses[cls]}")
print(h1)
sqlclass = cls
print("%-*s %-*s %-*s" % (
widths[0], l.colstate, widths[1], l.colexc, widths[2], l.colbase))
def get_base_exception(exc):
for cls in exc.__mro__:
if cls.__module__ == 'psycopg2':
return cls
if __name__ == '__main__':
sys.exit(main())

63
doc/src/tools/stitch_text.py Executable file
View File

@ -0,0 +1,63 @@
#! /usr/bin/env python
"""A script to stitch together the generated text files in the correct order.
"""
import os
import sys
def main():
if len(sys.argv) != 3:
sys.stderr.write("usage: %s index.rst text-dir\n")
return 2
_, index, txt_dir = sys.argv
for fb in iter_file_base(index):
emit(fb, txt_dir)
return 0
def iter_file_base(fn):
f = open(fn)
if sys.version_info[0] >= 3:
have_line = iter(f).__next__
else:
have_line = iter(f).next
while not have_line().startswith('.. toctree'):
pass
while have_line().strip().startswith(':'):
pass
yield os.path.splitext(os.path.basename(fn))[0]
n = 0
while True:
line = have_line()
if line.isspace():
continue
if line.startswith(".."):
break
n += 1
yield line.strip()
f.close()
if n < 5:
# maybe format changed?
raise Exception("Not enough files found. Format change in index.rst?")
def emit(basename, txt_dir):
f = open(os.path.join(txt_dir, basename + ".txt"))
for line in f:
line = line.replace("``", "'")
sys.stdout.write(line)
f.close()
# some space between sections
sys.stdout.write("\n\n")
if __name__ == '__main__':
sys.exit(main())

View File

@ -5,10 +5,6 @@
.. module:: psycopg2.tz .. module:: psycopg2.tz
.. deprecated:: 2.9
The module will be dropped in psycopg 2.10. Use `datetime.timezone`
instead.
This module holds two different tzinfo implementations that can be used as the This module holds two different tzinfo implementations that can be used as the
`tzinfo` argument to `~datetime.datetime` constructors, directly passed to `tzinfo` argument to `~datetime.datetime` constructors, directly passed to
Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in Psycopg functions or used to set the `cursor.tzinfo_factory` attribute in
@ -17,3 +13,4 @@ cursors.
.. autoclass:: psycopg2.tz.FixedOffsetTimezone .. autoclass:: psycopg2.tz.FixedOffsetTimezone
.. autoclass:: psycopg2.tz.LocalTimezone .. autoclass:: psycopg2.tz.LocalTimezone

View File

@ -48,7 +48,7 @@ The main entry points of Psycopg are:
- The class `connection` encapsulates a database session. It allows to: - The class `connection` encapsulates a database session. It allows to:
- create new `cursor` instances using the `~connection.cursor()` method to - create new `cursor`\s using the `~connection.cursor()` method to
execute database commands and queries, execute database commands and queries,
- terminate transactions using the methods `~connection.commit()` or - terminate transactions using the methods `~connection.commit()` or
@ -73,97 +73,68 @@ The main entry points of Psycopg are:
Passing parameters to SQL queries Passing parameters to SQL queries
--------------------------------- ---------------------------------
Psycopg converts Python variables to SQL values using their types: the Python Psycopg casts Python variables to SQL literals by type. Many standard Python types
type determines the function used to convert the object into a string are already `adapted to the correct SQL representation`__.
representation suitable for PostgreSQL. Many standard Python types are
already `adapted to the correct SQL representation`__.
.. __: python-types-adaptation_ .. __: python-types-adaptation_
Passing parameters to an SQL statement happens in functions such as Example: the Python function call::
`cursor.execute()` by using ``%s`` placeholders in the SQL statement, and
passing a sequence of values as the second argument of the function. For
example the Python function call::
>>> cur.execute(""" >>> cur.execute(
... INSERT INTO some_table (an_int, a_date, a_string) ... """INSERT INTO some_table (an_int, a_date, a_string)
... VALUES (%s, %s, %s); ... VALUES (%s, %s, %s);""",
... """,
... (10, datetime.date(2005, 11, 18), "O'Reilly")) ... (10, datetime.date(2005, 11, 18), "O'Reilly"))
is converted into a SQL command similar to: is converted into the SQL command::
.. code-block:: sql
INSERT INTO some_table (an_int, a_date, a_string) INSERT INTO some_table (an_int, a_date, a_string)
VALUES (10, '2005-11-18', 'O''Reilly'); VALUES (10, '2005-11-18', 'O''Reilly');
Named arguments are supported too using :samp:`%({name})s` placeholders in the Named arguments are supported too using :samp:`%({name})s` placeholders.
query and specifying the values into a mapping. Using named arguments allows Using named arguments the values can be passed to the query in any order and
to specify the values in any order and to repeat the same value in several many placeholders can use the same values::
places in the query::
>>> cur.execute(""" >>> cur.execute(
... INSERT INTO some_table (an_int, a_date, another_date, a_string) ... """INSERT INTO some_table (an_int, a_date, another_date, a_string)
... VALUES (%(int)s, %(date)s, %(date)s, %(str)s); ... VALUES (%(int)s, %(date)s, %(date)s, %(str)s);""",
... """,
... {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)}) ... {'int': 10, 'str': "O'Reilly", 'date': datetime.date(2005, 11, 18)})
Using characters ``%``, ``(``, ``)`` in the argument names is not supported.
When parameters are used, in order to include a literal ``%`` in the query you When parameters are used, in order to include a literal ``%`` in the query you
can use the ``%%`` string:: can use the ``%%`` string.
>>> cur.execute("SELECT (%s % 2) = 0 AS even", (10,)) # WRONG
>>> cur.execute("SELECT (%s %% 2) = 0 AS even", (10,)) # correct
While the mechanism resembles regular Python strings manipulation, there are a While the mechanism resembles regular Python strings manipulation, there are a
few subtle differences you should care about when passing parameters to a few subtle differences you should care about when passing parameters to a
query. query:
- The Python string operator ``%`` *must not be used*: the `~cursor.execute()` - The Python string operator ``%`` is not used: the `~cursor.execute()`
method accepts a tuple or dictionary of values as second parameter. method accepts a tuple or dictionary of values as second parameter.
|sql-warn|__: |sql-warn|__.
.. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values .. |sql-warn| replace:: **Never** use ``%`` or ``+`` to merge values
into queries into queries
.. __: sql-injection_ .. __: sql-injection_
>>> cur.execute("INSERT INTO numbers VALUES (%s, %s)" % (10, 20)) # WRONG - The variables placeholder must *always be a* ``%s``, even if a different
>>> cur.execute("INSERT INTO numbers VALUES (%s, %s)", (10, 20)) # correct placeholder (such as a ``%d`` for integers or ``%f`` for floats) may look
more appropriate::
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (42,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct
- For positional variables binding, *the second argument must always be a - For positional variables binding, *the second argument must always be a
sequence*, even if it contains a single variable (remember that Python sequence*, even if it contains a single variable. And remember that Python
requires a comma to create a single element tuple):: requires a comma to create a single element tuple::
>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG >>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG >>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct >>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct >>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct
- The placeholder *must not be quoted*. Psycopg will add quotes where needed:: - Only variable values should be bound via this method: it shouldn't be used
to set table or field names. For these elements, ordinary string formatting
should be used before running `~cursor.execute()`.
>>> cur.execute("INSERT INTO numbers VALUES ('%s')", (10,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (10,)) # correct
- The variables placeholder *must always be a* ``%s``, even if a different
placeholder (such as a ``%d`` for integers or ``%f`` for floats) may look
more appropriate::
>>> cur.execute("INSERT INTO numbers VALUES (%d)", (10,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (10,)) # correct
- Only query values should be bound via this method: it shouldn't be used to
merge table or field names to the query (Psycopg will try quoting the table
name as a string value, generating invalid SQL). If you need to generate
dynamically SQL queries (for instance choosing dynamically a table name)
you can use the facilities provided by the `psycopg2.sql` module::
>>> cur.execute("INSERT INTO %s VALUES (%s)", ('numbers', 10)) # WRONG
>>> cur.execute( # correct
... SQL("INSERT INTO {} VALUES (%s)").format(Identifier('numbers')),
... (10,))
.. index:: Security, SQL injection .. index:: Security, SQL injection
@ -173,15 +144,13 @@ query.
The problem with the query parameters The problem with the query parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The SQL representation of many data types is often different from their Python The SQL representation for many data types is often not the same of the Python
string representation. The typical example is with single quotes in strings: string representation. The classic example is with single quotes in
in SQL single quotes are used as string literal delimiters, so the ones strings: SQL uses them as string constants bounds and requires them to be
appearing inside the string itself must be escaped, whereas in Python single escaped, whereas in Python single quotes can be left unescaped in strings
quotes can be left unescaped if the string is delimited by double quotes. 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
Because of the difference, sometime subtle, between the data types problems::
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 >>> SQL = "INSERT INTO authors (name) VALUES ('%s');" # NEVER DO THIS
>>> data = ("O'Reilly", ) >>> data = ("O'Reilly", )
@ -190,16 +159,16 @@ Python strings concatenation, is a recipe for *terrible* problems::
LINE 1: INSERT INTO authors (name) VALUES ('O'Reilly') LINE 1: INSERT INTO authors (name) VALUES ('O'Reilly')
^ ^
If the variables containing the data to send to the database come from an If the variable containing the data to be sent to the database comes from an
untrusted source (such as a form published on a web site) an attacker could untrusted source (e.g. a form published on a web site) an attacker could
easily craft a malformed string, either gaining access to unauthorized data or easily craft a malformed string, either gaining access to unauthorized data or
performing destructive operations on the database. This form of attack is 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 called `SQL injection`_ and is known to be one of the most widespread forms of
attack to database servers. Before continuing, please print `this page`__ as a attack to servers. Before continuing, please print `this page`__ as a memo and
memo and hang it onto your desk. hang it onto your desk.
.. _SQL injection: https://en.wikipedia.org/wiki/SQL_injection .. _SQL injection: http://en.wikipedia.org/wiki/SQL_injection
.. __: https://xkcd.com/327/ .. __: http://xkcd.com/327/
Psycopg can `automatically convert Python objects to and from SQL Psycopg can `automatically convert Python objects to and from SQL
literals`__: using this feature your code will be more robust and literals`__: using this feature your code will be more robust and
@ -221,27 +190,6 @@ argument of the `~cursor.execute()` method::
>>> cur.execute(SQL, data) # Note: no % operator >>> cur.execute(SQL, data) # Note: no % operator
Values containing backslashes and LIKE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Unlike in Python, the backslash (`\\`) is not used as an escape
character *except* in patterns used with `LIKE` and `ILIKE` where they
are needed to escape the `%` and `_` characters.
This can lead to confusing situations::
>>> path = r'C:\Users\Bobby.Tables'
>>> cur.execute('INSERT INTO mytable(path) VALUES (%s)', (path,))
>>> cur.execute('SELECT * FROM mytable WHERE path LIKE %s', (path,))
>>> cur.fetchall()
[]
The solution is to specify an `ESCAPE` character of `''` (empty string)
in your `LIKE` query::
>>> cur.execute("SELECT * FROM mytable WHERE path LIKE %s ESCAPE ''", (path,))
.. index:: .. index::
single: Adaptation single: Adaptation
@ -294,8 +242,7 @@ types:
+--------------------+-------------------------+--------------------------+ +--------------------+-------------------------+--------------------------+
| `!date` | :sql:`date` | :ref:`adapt-date` | | `!date` | :sql:`date` | :ref:`adapt-date` |
+--------------------+-------------------------+ | +--------------------+-------------------------+ |
| `!time` | | :sql:`time` | | | `!time` | :sql:`time` | |
| | | :sql:`timetz` | |
+--------------------+-------------------------+ | +--------------------+-------------------------+ |
| `!datetime` | | :sql:`timestamp` | | | `!datetime` | | :sql:`timestamp` | |
| | | :sql:`timestamptz` | | | | | :sql:`timestamptz` | |
@ -313,10 +260,7 @@ types:
+--------------------+-------------------------+--------------------------+ +--------------------+-------------------------+--------------------------+
| Anything\ |tm| | :sql:`json` | :ref:`adapt-json` | | Anything\ |tm| | :sql:`json` | :ref:`adapt-json` |
+--------------------+-------------------------+--------------------------+ +--------------------+-------------------------+--------------------------+
| `~uuid.UUID` | :sql:`uuid` | :ref:`adapt-uuid` | | `uuid` | :sql:`uuid` | :ref:`adapt-uuid` |
+--------------------+-------------------------+--------------------------+
| `ipaddress` | | :sql:`inet` | :ref:`adapt-network` |
| objects | | :sql:`cidr` | |
+--------------------+-------------------------+--------------------------+ +--------------------+-------------------------+--------------------------+
.. |tm| unicode:: U+2122 .. |tm| unicode:: U+2122
@ -354,8 +298,8 @@ proper SQL literals::
Numbers adaptation Numbers adaptation
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
Python numeric objects `int`, `long`, `float`, `~decimal.Decimal` are Numeric objects: `int`, `long`, `float`, `~decimal.Decimal` are converted in
converted into a PostgreSQL numerical representation:: the PostgreSQL numerical representation::
>>> cur.mogrify("SELECT %s, %s, %s, %s;", (10, 10L, 10.0, Decimal("10.00"))) >>> cur.mogrify("SELECT %s, %s, %s, %s;", (10, 10L, 10.0, Decimal("10.00")))
'SELECT 10, 10, 10.0, 10.00;' 'SELECT 10, 10, 10.0, 10.00;'
@ -367,12 +311,12 @@ converted into `!Decimal`.
.. note:: .. note::
Sometimes you may prefer to receive :sql:`numeric` data as `!float` Sometimes you may prefer to receive :sql:`numeric` data as `!float`
instead, for performance reason or ease of manipulation: you can configure insted, for performance reason or ease of manipulation: you can configure
an adapter to :ref:`cast PostgreSQL numeric to Python float <faq-float>`. an adapter to :ref:`cast PostgreSQL numeric to Python float <faq-float>`.
This of course may imply a loss of precision. This of course may imply a loss of precision.
.. seealso:: `PostgreSQL numeric types .. seealso:: `PostgreSQL numeric types
<https://www.postgresql.org/docs/current/static/datatype-numeric.html>`__ <http://www.postgresql.org/docs/current/static/datatype-numeric.html>`__
.. index:: .. index::
@ -404,33 +348,33 @@ Unicode handling
Psycopg can exchange Unicode data with a PostgreSQL database. Python Psycopg can exchange Unicode data with a PostgreSQL database. Python
`!unicode` objects are automatically *encoded* in the client encoding `!unicode` objects are automatically *encoded* in the client encoding
defined on the database connection (the `PostgreSQL encoding`__, available in defined on the database connection (the `PostgreSQL encoding`__, available in
`connection.encoding`, is translated into a `Python encoding`__ using the `connection.encoding`, is translated into a `Python codec`__ using the
`~psycopg2.extensions.encodings` mapping):: `~psycopg2.extensions.encodings` mapping)::
>>> print(u, type(u)) >>> print u, type(u)
àèìòù€ <type 'unicode'> àèìòù€ <type 'unicode'>
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s,%s);", (74, u)) >>> cur.execute("INSERT INTO test (num, data) VALUES (%s,%s);", (74, u))
.. __: https://www.postgresql.org/docs/current/static/multibyte.html .. __: http://www.postgresql.org/docs/current/static/multibyte.html
.. __: https://docs.python.org/library/codecs.html .. __: http://docs.python.org/library/codecs.html#standard-encodings
When reading data from the database, in Python 2 the strings returned are When reading data from the database, in Python 2 the strings returned are
usually 8 bit `!str` objects encoded in the database client encoding:: usually 8 bit `!str` objects encoded in the database client encoding::
>>> print(conn.encoding) >>> print conn.encoding
UTF8 UTF8
>>> cur.execute("SELECT data FROM test WHERE num = 74") >>> cur.execute("SELECT data FROM test WHERE num = 74")
>>> x = cur.fetchone()[0] >>> x = cur.fetchone()[0]
>>> print(x, type(x), repr(x)) >>> print x, type(x), repr(x)
àèìòù€ <type 'str'> '\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9\xe2\x82\xac' àèìòù€ <type 'str'> '\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9\xe2\x82\xac'
>>> conn.set_client_encoding('LATIN9') >>> conn.set_client_encoding('LATIN9')
>>> cur.execute("SELECT data FROM test WHERE num = 74") >>> cur.execute("SELECT data FROM test WHERE num = 74")
>>> x = cur.fetchone()[0] >>> x = cur.fetchone()[0]
>>> print(type(x), repr(x)) >>> print type(x), repr(x)
<type 'str'> '\xe0\xe8\xec\xf2\xf9\xa4' <type 'str'> '\xe0\xe8\xec\xf2\xf9\xa4'
In Python 3 instead the strings are automatically *decoded* in the connection In Python 3 instead the strings are automatically *decoded* in the connection
@ -442,7 +386,7 @@ In Python 2 you must register a :ref:`typecaster
>>> cur.execute("SELECT data FROM test WHERE num = 74") >>> cur.execute("SELECT data FROM test WHERE num = 74")
>>> x = cur.fetchone()[0] >>> x = cur.fetchone()[0]
>>> print(x, type(x), repr(x)) >>> print x, type(x), repr(x)
àèìòù€ <type 'unicode'> u'\xe0\xe8\xec\xf2\xf9\u20ac' àèìòù€ <type 'unicode'> u'\xe0\xe8\xec\xf2\xf9\u20ac'
In the above example, the `~psycopg2.extensions.UNICODE` typecaster is In the above example, the `~psycopg2.extensions.UNICODE` typecaster is
@ -457,29 +401,13 @@ the connection or globally: see the function
Unicode, you can register the related typecasters globally as soon as Unicode, you can register the related typecasters globally as soon as
Psycopg is imported:: Psycopg is imported::
import psycopg2
import psycopg2.extensions import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY)
and forget about this story. and forget about this story.
.. note::
In some cases, on Python 3, you may want to receive `!bytes` instead of
`!str`, without undergoing to any decoding. This is especially the case if
the data in the database is in mixed encoding. The
`~psycopg2.extensions.BYTES` caster is what you neeed::
import psycopg2.extensions
psycopg2.extensions.register_type(psycopg2.extensions.BYTES, conn)
psycopg2.extensions.register_type(psycopg2.extensions.BYTESARRAY, conn)
cur = conn.cursor()
cur.execute("select %s::text", (u"€",))
cur.fetchone()[0]
b'\xe2\x82\xac'
.. versionadded: 2.8
.. index:: .. index::
single: Buffer; Adaptation single: Buffer; Adaptation
@ -494,15 +422,17 @@ the connection or globally: see the function
Binary adaptation Binary adaptation
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
Python types representing binary objects are converted into PostgreSQL binary Binary types: Python types representing binary objects are converted into
string syntax, suitable for :sql:`bytea` fields. Such types are `buffer` PostgreSQL binary string syntax, suitable for :sql:`bytea` fields. Such
(only available in Python 2), `memoryview`, `bytearray`, and `bytes` (only in types are `buffer` (only available in Python 2), `memoryview` (available
Python 3: the name is available in Python 2 but it's only an alias for the from Python 2.7), `bytearray` (available from Python 2.6) and `bytes`
type `!str`). Any object implementing the `Revised Buffer Protocol`__ should (only from Python 3: the name is available from Python 2.6 but it's only an
be usable as binary type. Received data is returned as `!buffer` (in Python 2) alias for the type `!str`). Any object implementing the `Revised Buffer
Protocol`__ should be usable as binary type where the protocol is supported
(i.e. from Python 2.6). Received data is returned as `!buffer` (in Python 2)
or `!memoryview` (in Python 3). or `!memoryview` (in Python 3).
.. __: https://www.python.org/dev/peps/pep-3118/ .. __: http://www.python.org/dev/peps/pep-3118/
.. versionchanged:: 2.4 .. versionchanged:: 2.4
only strings were supported before. only strings were supported before.
@ -531,8 +461,8 @@ or `!memoryview` (in Python 3).
server configuration file or in the client session (using a query such as server configuration file or in the client session (using a query such as
``SET bytea_output TO escape;``) before receiving binary data. ``SET bytea_output TO escape;``) before receiving binary data.
.. __: https://www.postgresql.org/docs/current/static/datatype-binary.html .. __: http://www.postgresql.org/docs/current/static/datatype-binary.html
.. __: https://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT .. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
.. index:: .. index::
@ -540,16 +470,18 @@ or `!memoryview` (in Python 3).
single: Date objects; Adaptation single: Date objects; Adaptation
single: Time objects; Adaptation single: Time objects; Adaptation
single: Interval objects; Adaptation single: Interval objects; Adaptation
single: mx.DateTime; Adaptation
.. _adapt-date: .. _adapt-date:
Date/Time objects adaptation Date/Time objects adaptation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Python builtin `~datetime.datetime`, `~datetime.date`, Date and time objects: builtin `~datetime.datetime`, `~datetime.date`,
`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's `~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time[tz]`, :sql:`interval` data types. :sql:`timestamp[tz]`, :sql:`date`, :sql:`time`, :sql:`interval` data types.
Time zones are supported too. Time zones are supported too. The Egenix `mx.DateTime`_ objects are adapted
the same way::
>>> dt = datetime.datetime.now() >>> dt = datetime.datetime.now()
>>> dt >>> dt
@ -562,8 +494,7 @@ Time zones are supported too.
"SELECT '38 days 6027.425337 seconds';" "SELECT '38 days 6027.425337 seconds';"
.. seealso:: `PostgreSQL date/time types .. seealso:: `PostgreSQL date/time types
<https://www.postgresql.org/docs/current/static/datatype-datetime.html>`__ <http://www.postgresql.org/docs/current/static/datatype-datetime.html>`__
.. index:: .. index::
single: Time Zones single: Time Zones
@ -574,95 +505,29 @@ Time zones handling
''''''''''''''''''' '''''''''''''''''''
The PostgreSQL type :sql:`timestamp with time zone` (a.k.a. The PostgreSQL type :sql:`timestamp with time zone` (a.k.a.
:sql:`timestamptz`) is converted into Python `~datetime.datetime` objects. :sql:`timestamptz`) is converted into Python `~datetime.datetime` objects with
a `~datetime.datetime.tzinfo` attribute set to a
`~psycopg2.tz.FixedOffsetTimezone` instance.
>>> cur.execute("SET TIME ZONE 'Europe/Rome'") # UTC + 1 hour >>> cur.execute("SET TIME ZONE 'Europe/Rome';") # UTC + 1 hour
>>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz") >>> cur.execute("SELECT '2010-01-01 10:30:45'::timestamptz;")
>>> cur.fetchone()[0] >>> cur.fetchone()[0].tzinfo
datetime.datetime(2010, 1, 1, 10, 30, 45, psycopg2.tz.FixedOffsetTimezone(offset=60, name=None)
tzinfo=datetime.timezone(datetime.timedelta(seconds=3600)))
.. note:: Note that only time zones with an integer number of minutes are supported:
this is a limitation of the Python `datetime` module. A few historical time
zones had seconds in the UTC offset: these time zones will have the offset
rounded to the nearest minute, with an error of up to 30 seconds.
Before Python 3.7, the `datetime` module only supported timezones with an >>> cur.execute("SET TIME ZONE 'Asia/Calcutta';") # offset was +5:53:20
integer number of minutes. A few historical time zones had seconds in the >>> cur.execute("SELECT '1930-01-01 10:30:45'::timestamptz;")
UTC offset: these time zones will have the offset rounded to the nearest >>> cur.fetchone()[0].tzinfo
minute, with an error of up to 30 seconds, on Python versions before 3.7. psycopg2.tz.FixedOffsetTimezone(offset=353, name=None)
>>> cur.execute("SET TIME ZONE 'Asia/Calcutta'") # offset was +5:21:10
>>> cur.execute("SELECT '1900-01-01 10:30:45'::timestamptz")
>>> cur.fetchone()[0].tzinfo
# On Python 3.6: 5h, 21m
datetime.timezone(datetime.timedelta(0, 19260))
# On Python 3.7 and following: 5h, 21m, 10s
datetime.timezone(datetime.timedelta(seconds=19270))
.. versionchanged:: 2.2.2 .. versionchanged:: 2.2.2
timezones with seconds are supported (with rounding). Previously such timezones with seconds are supported (with rounding). Previously such
timezones raised an error. timezones raised an error. In order to deal with them in previous
versions use `psycopg2.extras.register_tstz_w_secs()`.
.. versionchanged:: 2.9
timezones with seconds are supported without rounding.
.. versionchanged:: 2.9
use `datetime.timezone` as default tzinfo object instead of
`~psycopg2.tz.FixedOffsetTimezone`.
.. index::
double: Date objects; Infinite
.. _infinite-dates-handling:
Infinite dates handling
'''''''''''''''''''''''
PostgreSQL can store the representation of an "infinite" date, timestamp, or
interval. Infinite dates are not available to Python, so these objects are
mapped to `!date.max`, `!datetime.max`, `!interval.max`. Unfortunately the
mapping cannot be bidirectional so these dates will be stored back into the
database with their values, such as :sql:`9999-12-31`.
It is possible to create an alternative adapter for dates and other objects
to map `date.max` to :sql:`infinity`, for instance::
class InfDateAdapter:
def __init__(self, wrapped):
self.wrapped = wrapped
def getquoted(self):
if self.wrapped == datetime.date.max:
return b"'infinity'::date"
elif self.wrapped == datetime.date.min:
return b"'-infinity'::date"
else:
return psycopg2.extensions.DateFromPy(self.wrapped).getquoted()
psycopg2.extensions.register_adapter(datetime.date, InfDateAdapter)
Of course it will not be possible to write the value of `date.max` in the
database anymore: :sql:`infinity` will be stored instead.
.. _time-handling:
Time handling
'''''''''''''
The PostgreSQL :sql:`time` and Python `~datetime.time` types are not
fully bidirectional.
Within PostgreSQL, the :sql:`time` type's maximum value of ``24:00:00`` is
treated as 24-hours later than the minimum value of ``00:00:00``.
>>> cur.execute("SELECT '24:00:00'::time - '00:00:00'::time")
>>> cur.fetchone()[0]
datetime.timedelta(days=1)
However, Python's `!time` only supports times until ``23:59:59``.
Retrieving a value of ``24:00:00`` results in a `!time` of ``00:00:00``.
>>> cur.execute("SELECT '24:00:00'::time, '00:00:00'::time")
>>> cur.fetchone()
(datetime.time(0, 0), datetime.time(0, 0))
.. _adapt-list: .. _adapt-list:
@ -690,12 +555,12 @@ Python lists are converted into PostgreSQL :sql:`ARRAY`\ s::
Furthermore :sql:`ANY` can also work with empty lists, whereas :sql:`IN ()` Furthermore :sql:`ANY` can also work with empty lists, whereas :sql:`IN ()`
is a SQL syntax error. is a SQL syntax error.
.. __: https://www.postgresql.org/docs/current/static/functions-subquery.html#FUNCTIONS-SUBQUERY-ANY-SOME .. __: http://www.postgresql.org/docs/current/static/functions-subquery.html#FUNCTIONS-SUBQUERY-ANY-SOME
.. note:: .. note::
Reading back from PostgreSQL, arrays are converted to lists of Python Reading back from PostgreSQL, arrays are converted to lists of Python
objects as expected, but only if the items are of a known type. objects as expected, but only if the items are of a known known type.
Arrays of unknown types are returned as represented by the database (e.g. Arrays of unknown types are returned as represented by the database (e.g.
``{a,b,c}``). If you want to convert the items into Python objects you can ``{a,b,c}``). If you want to convert the items into Python objects you can
easily create a typecaster for :ref:`array of unknown types easily create a typecaster for :ref:`array of unknown types
@ -711,7 +576,7 @@ Tuples adaptation
double: Tuple; Adaptation double: Tuple; Adaptation
single: IN operator single: IN operator
Python tuples are converted into a syntax suitable for the SQL :sql:`IN` Python tuples are converted in a syntax suitable for the SQL :sql:`IN`
operator and to represent a composite type:: operator and to represent a composite type::
>>> cur.mogrify("SELECT %s IN %s;", (10, (10, 20, 30))) >>> cur.mogrify("SELECT %s IN %s;", (10, (10, 20, 30)))
@ -758,30 +623,24 @@ until a call to the `~connection.rollback()` method.
The connection is responsible for terminating its transaction, calling either The connection is responsible for terminating its transaction, calling either
the `~connection.commit()` or `~connection.rollback()` method. Committed the `~connection.commit()` or `~connection.rollback()` method. Committed
changes are immediately made persistent in the database. If the connection changes are immediately made persistent into the database. Closing the
is closed (using the `~connection.close()` method) or destroyed (using `!del` connection using the `~connection.close()` method or destroying the
or by letting it fall out of scope) while a transaction is in progress, the connection object (using `!del` or letting it fall out of scope)
server will discard the transaction. However doing so is not advisable: will result in an implicit rollback.
middleware such as PgBouncer_ may see the connection closed uncleanly and
dispose of it.
.. _PgBouncer: http://www.pgbouncer.org/
It is possible to set the connection in *autocommit* mode: this way all the It is possible to set the connection in *autocommit* mode: this way all the
commands executed will be immediately committed and no rollback is possible. A commands executed will be immediately committed and no rollback is possible. A
few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`, :sql:`CALL` on few commands (e.g. :sql:`CREATE DATABASE`, :sql:`VACUUM`...) require to be run
`stored procedures`__ using transaction control...) require to be run
outside any transaction: in order to be able to run these commands from outside any transaction: in order to be able to run these commands from
Psycopg, the connection must be in autocommit mode: you can use the Psycopg, the connection must be in autocommit mode: you can use the
`~connection.autocommit` property. `~connection.autocommit` property (`~connection.set_isolation_level()` in
older versions).
.. __: https://www.postgresql.org/docs/current/xproc.html
.. warning:: .. warning::
By default even a simple :sql:`SELECT` will start a transaction: in By default even a simple :sql:`SELECT` will start a transaction: in
long-running programs, if no further action is taken, the session will long-running programs, if no further action is taken, the session will
remain "idle in transaction", an undesirable condition for several remain "idle in transaction", a condition non desiderable for several
reasons (locks are held by the session, tables bloat...). For long lived 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 scripts, either make sure to terminate a transaction as soon as possible or
use an autocommit connection. use an autocommit connection.
@ -795,8 +654,6 @@ the details.
.. index:: .. index::
single: with statement single: with statement
.. _with:
``with`` statement ``with`` statement
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
@ -809,41 +666,13 @@ managers* and can be used with the ``with`` statement::
When a connection exits the ``with`` block, if no exception has been raised by 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 the block, the transaction is committed. In case of exception the transaction
is rolled back. 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.
When a cursor exits the ``with`` block it is closed, releasing any resource 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. eventually associated with it. The state of the transaction is not affected.
A connection can be used in more than one ``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()
.. warning::
Unlike file objects or other resources, exiting the connection's
``with`` block **doesn't close the connection**, but only the transaction
associated to it. If you want to make sure the connection is closed after
a certain point, you should still use a try-catch block::
conn = psycopg2.connect(DSN)
try:
# connection usage
finally:
conn.close()
.. versionchanged:: 2.9
``with connection`` starts a transaction also on autocommit connections.
.. index:: .. index::
@ -860,7 +689,7 @@ Server side cursors
When a database query is executed, the Psycopg `cursor` usually fetches When a database query is executed, the Psycopg `cursor` usually fetches
all the records returned by the backend, transferring them to the client all the records returned by the backend, transferring them to the client
process. If the query returns a huge amount of data, a proportionally large process. If the query returned an huge amount of data, a proportionally large
amount of memory will be allocated by the client. amount of memory will be allocated by the client.
If the dataset is too large to be practically handled on the client side, it is If the dataset is too large to be practically handled on the client side, it is
@ -890,8 +719,8 @@ you may decrease this value if you are dealing with huge records.
Named cursors are usually created :sql:`WITHOUT HOLD`, meaning they live only Named cursors are usually created :sql:`WITHOUT HOLD`, meaning they live only
as long as the current transaction. Trying to fetch from a named cursor after as long as the current transaction. Trying to fetch from a named cursor after
a `~connection.commit()` or to create a named cursor when the connection a `~connection.commit()` or to create a named cursor when the `connection`
is in `~connection.autocommit` mode will result in an exception. transaction isolation level is set to `AUTOCOMMIT` will result in an exception.
It is possible to create a :sql:`WITH HOLD` cursor by specifying a `!True` It is possible to create a :sql:`WITH HOLD` cursor by specifying a `!True`
value for the `withhold` parameter to `~connection.cursor()` or by setting the value for the `withhold` parameter to `~connection.cursor()` or by setting the
`~cursor.withhold` attribute to `!True` before calling `~cursor.execute()` on `~cursor.withhold` attribute to `!True` before calling `~cursor.execute()` on
@ -906,9 +735,7 @@ lifetime extends well after `~connection.commit()`, calling
It is also possible to use a named cursor to consume a cursor created It is also possible to use a named cursor to consume a cursor created
in some other way than using the |DECLARE| executed by in some other way than using the |DECLARE| executed by
`~cursor.execute()`. For example, you may have a PL/pgSQL function `~cursor.execute()`. For example, you may have a PL/pgSQL function
returning a cursor: returning a cursor::
.. code-block:: postgres
CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS $$ CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS $$
BEGIN BEGIN
@ -936,7 +763,7 @@ lifetime extends well after `~connection.commit()`, calling
.. |DECLARE| replace:: :sql:`DECLARE` .. |DECLARE| replace:: :sql:`DECLARE`
.. _DECLARE: https://www.postgresql.org/docs/current/static/sql-declare.html .. _DECLARE: http://www.postgresql.org/docs/current/static/sql-declare.html
@ -966,7 +793,7 @@ forked processes`__, so when using a module such as `multiprocessing` or a
forking web deploy method such as FastCGI make sure to create the connections forking web deploy method such as FastCGI make sure to create the connections
*after* the fork. *after* the fork.
.. __: https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT .. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNECT
Connections shouldn't be shared either by different green threads: see Connections shouldn't be shared either by different green threads: see
:ref:`green-support` for further details. :ref:`green-support` for further details.
@ -983,19 +810,11 @@ Using COPY TO and COPY FROM
Psycopg `cursor` objects provide an interface to the efficient Psycopg `cursor` objects provide an interface to the efficient
PostgreSQL |COPY|__ command to move data from files to tables and back. PostgreSQL |COPY|__ command to move data from files to tables and back.
Currently no adaptation is provided between Python and PostgreSQL types on
|COPY|: the file can be any Python file-like object but its format must be in
the format accepted by `PostgreSQL COPY command`__ (data format, escaped
characters, etc).
.. __: COPY_
The methods exposed are: The methods exposed are:
`~cursor.copy_from()` `~cursor.copy_from()`
Reads data *from* a file-like object appending them to a database table Reads data *from* a file-like object appending them to a database table
(:sql:`COPY table FROM file` syntax). The source file must provide both (:sql:`COPY table FROM file` syntax). The source file must have both
`!read()` and `!readline()` method. `!read()` and `!readline()` method.
`~cursor.copy_to()` `~cursor.copy_to()`
@ -1010,7 +829,7 @@ Please refer to the documentation of the single methods for details and
examples. examples.
.. |COPY| replace:: :sql:`COPY` .. |COPY| replace:: :sql:`COPY`
.. __: https://www.postgresql.org/docs/current/static/sql-copy.html .. __: http://www.postgresql.org/docs/current/static/sql-copy.html
@ -1027,7 +846,7 @@ access to user data that is stored in a special large-object structure. They
are useful with data values too large to be manipulated conveniently as a are useful with data values too large to be manipulated conveniently as a
whole. whole.
.. __: https://www.postgresql.org/docs/current/static/largeobjects.html .. __: http://www.postgresql.org/docs/current/static/largeobjects.html
Psycopg allows access to the large object using the Psycopg allows access to the large object using the
`~psycopg2.extensions.lobject` class. Objects are generated using the `~psycopg2.extensions.lobject` class. Objects are generated using the
@ -1038,23 +857,9 @@ Psycopg large object support efficient import/export with file system files
using the |lo_import|_ and |lo_export|_ libpq functions. using the |lo_import|_ and |lo_export|_ libpq functions.
.. |lo_import| replace:: `!lo_import()` .. |lo_import| replace:: `!lo_import()`
.. _lo_import: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT .. _lo_import: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-IMPORT
.. |lo_export| replace:: `!lo_export()` .. |lo_export| replace:: `!lo_export()`
.. _lo_export: https://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT .. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
.. versionchanged:: 2.6
added support for large objects greater 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 conditions above are verified), the `psycopg2.__version__` constant
will contain the ``lo64`` flag. If any of the condition is not met
several `!lobject` methods will fail if the arguments exceed 2GB.
@ -1104,5 +909,6 @@ transactions produced by a Java program.
For further details see the documentation for the above methods. For further details see the documentation for the above methods.
.. __: https://publications.opengroup.org/c193 .. __: http://www.opengroup.org/bookstore/catalog/c193.htm
.. __: https://jdbc.postgresql.org/ .. __: http://jdbc.postgresql.org/

89
examples/binary.py Normal file
View File

@ -0,0 +1,89 @@
# binary.py - working with binary data
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)")
except:
conn.rollback()
curs.execute("DROP TABLE test_binary")
curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)")
conn.commit()
# first we try two inserts, one with an explicit Binary call and the other
# using a buffer on a file object.
data1 = {'id':1, 'name':'somehackers.jpg',
'img':psycopg2.Binary(open('somehackers.jpg').read())}
data2 = {'id':2, 'name':'whereareyou.jpg',
'img':buffer(open('whereareyou.jpg').read())}
curs.execute("""INSERT INTO test_binary
VALUES (%(id)s, %(name)s, %(img)s)""", data1)
curs.execute("""INSERT INTO test_binary
VALUES (%(id)s, %(name)s, %(img)s)""", data2)
# now we try to extract the images as simple text strings
print "Extracting the images as strings..."
curs.execute("SELECT * FROM test_binary")
for row in curs.fetchall():
name, ext = row[1].split('.')
new_name = name + '_S.' + ext
print " writing %s to %s ..." % (name+'.'+ext, new_name),
open(new_name, 'wb').write(row[2])
print "done"
print " python type of image data is", type(row[2])
# extract exactly the same data but using a binary cursor
print "Extracting the images using a binary cursor:"
curs.execute("""DECLARE zot CURSOR FOR
SELECT img, name FROM test_binary FOR READ ONLY""")
curs.execute("""FETCH ALL FROM zot""")
for row in curs.fetchall():
name, ext = row[1].split('.')
new_name = name + '_B.' + ext
print " writing %s to %s ..." % (name+'.'+ext, new_name),
open(new_name, 'wb').write(row[0])
print "done"
print " python type of image data is", type(row[0])
# this rollback is required because we can't drop a table with a binary cusor
# declared and still open
conn.rollback()
curs.execute("DROP TABLE test_binary")
conn.commit()
print "\nNow try to load the new images, to check it worked!"

177
examples/copy_from.py Normal file
View File

@ -0,0 +1,177 @@
# copy_from.py -- example about copy_from
#
# Copyright (C) 2002 Tom Jenkins <tjenkins@devis.com>
# Copyright (C) 2005 Federico Di Gregorio <fog@initd.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import os
import StringIO
import psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
except:
conn.rollback()
curs.execute("DROP TABLE test_copy")
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
conn.commit()
# copy_from with default arguments, from open file
io = open('copy_from.txt', 'wr')
data = ['Tom\tJenkins\t37\n',
'Madonna\t\\N\t45\n',
'Federico\tDi Gregorio\t\\N\n']
io.writelines(data)
io.close()
io = open('copy_from.txt', 'r')
curs.copy_from(io, 'test_copy')
print "1) Copy %d records from file object " % len(data) + \
"using defaults (sep: \\t and null = \\N)"
io.close()
curs.execute("SELECT * FROM test_copy")
rows = curs.fetchall()
print " Select returned %d rows" % len(rows)
for r in rows:
print " %s %s\t%s" % (r[0], r[1], r[2])
curs.execute("delete from test_copy")
conn.commit()
# copy_from using custom separator, from open file
io = open('copy_from.txt', 'wr')
data = ['Tom:Jenkins:37\n',
'Madonna:\N:45\n',
'Federico:Di Gregorio:\N\n']
io.writelines(data)
io.close()
io = open('copy_from.txt', 'r')
curs.copy_from(io, 'test_copy', ':')
print "2) Copy %d records from file object using sep = :" % len(data)
io.close()
curs.execute("SELECT * FROM test_copy")
rows = curs.fetchall()
print " Select returned %d rows" % len(rows)
for r in rows:
print " %s %s\t%s" % (r[0], r[1], r[2])
curs.execute("delete from test_copy")
conn.commit()
# copy_from using custom null identifier, from open file
io = open('copy_from.txt', 'wr')
data = ['Tom\tJenkins\t37\n',
'Madonna\tNULL\t45\n',
'Federico\tDi Gregorio\tNULL\n']
io.writelines(data)
io.close()
io = open('copy_from.txt', 'r')
curs.copy_from(io, 'test_copy', null='NULL')
print "3) Copy %d records from file object using null = NULL" % len(data)
io.close()
curs.execute("SELECT * FROM test_copy")
rows = curs.fetchall()
print " Select using cursor returned %d rows" % len(rows)
for r in rows:
print " %s %s\t%s" % (r[0], r[1], r[2])
curs.execute("delete from test_copy")
conn.commit()
# copy_from using custom separator and null identifier
io = open('copy_from.txt', 'wr')
data = ['Tom:Jenkins:37\n', 'Madonna:NULL:45\n', 'Federico:Di Gregorio:NULL\n']
io.writelines(data)
io.close()
io = open('copy_from.txt', 'r')
curs.copy_from(io, 'test_copy', ':', 'NULL')
print "4) Copy %d records from file object " % len(data) + \
"using sep = : and null = NULL"
io.close()
curs.execute("SELECT * FROM test_copy")
rows = curs.fetchall()
print " Select using cursor returned %d rows" % len(rows)
for r in rows:
print " %s %s\t%s" % (r[0], r[1], r[2])
curs.execute("delete from test_copy")
conn.commit()
# anything can be used as a file if it has .read() and .readline() methods
data = StringIO.StringIO()
data.write('\n'.join(['Tom\tJenkins\t37',
'Madonna\t\N\t45',
'Federico\tDi Gregorio\t\N']))
data.seek(0)
curs.copy_from(data, 'test_copy')
print "5) Copy 3 records from StringIO object using defaults"
curs.execute("SELECT * FROM test_copy")
rows = curs.fetchall()
print " Select using cursor returned %d rows" % len(rows)
for r in rows:
print " %s %s\t%s" % (r[0], r[1], r[2])
curs.execute("delete from test_copy")
conn.commit()
# simple error test
print "6) About to raise an error"
data = StringIO.StringIO()
data.write('\n'.join(['Tom\tJenkins\t37',
'Madonna\t\N\t45',
'Federico\tDi Gregorio\taaa']))
data.seek(0)
try:
curs.copy_from(data, 'test_copy')
except StandardError, err:
conn.rollback()
print " Caught error (as expected):\n", err
conn.rollback()
curs.execute("DROP TABLE test_copy")
os.unlink('copy_from.txt')
conn.commit()

103
examples/copy_to.py Normal file
View File

@ -0,0 +1,103 @@
# copy_to.py -- example about copy_to
#
# Copyright (C) 2002 Tom Jenkins <tjenkins@devis.com>
# Copyright (C) 2005 Federico Di Gregorio <fog@initd.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import os
import StringIO
import psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
except:
conn.rollback()
curs.execute("DROP TABLE test_copy")
curs.execute("CREATE TABLE test_copy (fld1 text, fld2 text, fld3 int4)")
conn.commit()
# demostrate copy_to functionality
data = [('Tom', 'Jenkins', '37'),
('Madonna', None, '45'),
('Federico', 'Di Gregorio', None)]
query = "INSERT INTO test_copy VALUES (%s, %s, %s)"
curs.executemany(query, data)
conn.commit()
# copy_to using defaults
io = open('copy_to.txt', 'w')
curs.copy_to(io, 'test_copy')
print "1) Copy %d records into file object using defaults: " % len (data) + \
"sep = \\t and null = \\N"
io.close()
rows = open('copy_to.txt', 'r').readlines()
print " File has %d rows:" % len(rows)
for r in rows:
print " ", r,
# copy_to using custom separator
io = open('copy_to.txt', 'w')
curs.copy_to(io, 'test_copy', ':')
print "2) Copy %d records into file object using sep = :" % len(data)
io.close()
rows = open('copy_to.txt', 'r').readlines()
print " File has %d rows:" % len(rows)
for r in rows:
print " ", r,
# copy_to using custom null identifier
io = open('copy_to.txt', 'w')
curs.copy_to(io, 'test_copy', null='NULL')
print "3) Copy %d records into file object using null = NULL" % len(data)
io.close()
rows = open('copy_to.txt', 'r').readlines()
print " File has %d rows:" % len(rows)
for r in rows:
print " ", r,
# copy_to using custom separator and null identifier
io = open('copy_to.txt', 'w')
curs.copy_to(io, 'test_copy', ':', 'NULL')
print "4) Copy %d records into file object using sep = : and null ) NULL" % \
len(data)
io.close()
rows = open('copy_to.txt', 'r').readlines()
print " File has %d rows:" % len(rows)
for r in rows:
print " ", r,
curs.execute("DROP TABLE test_copy")
os.unlink('copy_to.txt')
conn.commit()

63
examples/cursor.py Normal file
View File

@ -0,0 +1,63 @@
# cursor.py - how to subclass the cursor type
#
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
import psycopg2.extensions
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dsn:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
class NoDataError(psycopg2.ProgrammingError):
"""Exception that will be raised by our cursor."""
pass
class Cursor(psycopg2.extensions.cursor):
"""A custom cursor."""
def fetchone(self):
"""Like fetchone but raise an exception if no data is available.
Note that to have .fetchmany() and .fetchall() to raise the same
exception we'll have to override them too; even if internally psycopg
uses the same function to fetch rows, the code path from Python is
different.
"""
d = psycopg2.extensions.cursor.fetchone(self)
if d is None:
raise NoDataError("no more data")
return d
curs = conn.cursor(cursor_factory=Cursor)
curs.execute("SELECT 1 AS foo")
print "Result of fetchone():", curs.fetchone()
# now let's raise the exception
try:
curs.fetchone()
except NoDataError, err:
print "Exception caught:", err
conn.rollback()

144
examples/dialtone.py Normal file
View File

@ -0,0 +1,144 @@
"""
This example/recipe has been contributed by Valentino Volonghi (dialtone)
Mapping arbitrary objects to a PostgreSQL database with psycopg2
- Problem
You need to store arbitrary objects in a PostgreSQL database without being
intrusive for your classes (don't want inheritance from an 'Item' or
'Persistent' object).
- Solution
"""
from datetime import datetime
import psycopg2
from psycopg2.extensions import adapt, register_adapter
try:
sorted()
except:
def sorted(seq):
seq.sort()
return seq
# Here is the adapter for every object that we may ever need to
# insert in the database. It receives the original object and does
# its job on that instance
class ObjectMapper(object):
def __init__(self, orig, curs=None):
self.orig = orig
self.tmp = {}
self.items, self.fields = self._gatherState()
def _gatherState(self):
adaptee_name = self.orig.__class__.__name__
fields = sorted([(field, getattr(self.orig, field))
for field in persistent_fields[adaptee_name]])
items = []
for item, value in fields:
items.append(item)
return items, fields
def getTableName(self):
return self.orig.__class__.__name__
def getMappedValues(self):
tmp = []
for i in self.items:
tmp.append("%%(%s)s"%i)
return ", ".join(tmp)
def getValuesDict(self):
return dict(self.fields)
def getFields(self):
return self.items
def generateInsert(self):
qry = "INSERT INTO"
qry += " " + self.getTableName() + " ("
qry += ", ".join(self.getFields()) + ") VALUES ("
qry += self.getMappedValues() + ")"
return qry, self.getValuesDict()
# Here are the objects
class Album(object):
id = 0
def __init__(self):
self.creation_time = datetime.now()
self.album_id = self.id
Album.id = Album.id + 1
self.binary_data = buffer('12312312312121')
class Order(object):
id = 0
def __init__(self):
self.items = ['rice','chocolate']
self.price = 34
self.order_id = self.id
Order.id = Order.id + 1
register_adapter(Album, ObjectMapper)
register_adapter(Order, ObjectMapper)
# Describe what is needed to save on each object
# This is actually just configuration, you can use xml with a parser if you
# like to have plenty of wasted CPU cycles ;P.
persistent_fields = {'Album': ['album_id', 'creation_time', 'binary_data'],
'Order': ['order_id', 'items', 'price']
}
print adapt(Album()).generateInsert()
print adapt(Album()).generateInsert()
print adapt(Album()).generateInsert()
print adapt(Order()).generateInsert()
print adapt(Order()).generateInsert()
print adapt(Order()).generateInsert()
"""
- Discussion
Psycopg 2 has a great new feature: adaptation. The big thing about
adaptation is that it enables the programmer to glue most of the
code out there without many difficulties.
This recipe tries to focus attention on a way to generate SQL queries to
insert completely new objects inside a database. As you can see objects do
not know anything about the code that is handling them. We specify all the
fields that we need for each object through the persistent_fields dict.
The most important lines of this recipe are:
register_adapter(Album, ObjectMapper)
register_adapter(Order, ObjectMapper)
In these lines we notify the system that when we call adapt with an Album instance
as an argument we want it to istantiate ObjectMapper passing the Album instance
as argument (self.orig in the ObjectMapper class).
The output is something like this (for each call to generateInsert):
('INSERT INTO Album (album_id, binary_data, creation_time) VALUES
(%(album_id)s, %(binary_data)s, %(creation_time)s)',
{'binary_data': <read-only buffer for 0x402de070, ...>,
'creation_time': datetime.datetime(2004, 9, 10, 20, 48, 29, 633728),
'album_id': 1}
)
This is a tuple of {SQL_QUERY, FILLING_DICT}, and all the quoting/converting
stuff (from python's datetime to postgres s and from python's buffer to
postgres' blob) is handled with the same adaptation process hunder the hood
by psycopg2.
At last, just notice that ObjectMapper is working for both Album and Order
instances without any glitches at all, and both classes could have easily been
coming from closed source libraries or C coded ones (which are not easily
modified), whereas a common pattern in todays ORMs or OODBs is to provide
a basic 'Persistent' object that already knows how to store itself in the
database.
"""

65
examples/dict.py Normal file
View File

@ -0,0 +1,65 @@
# dict.py - using DictCUrsor/DictRow
#
# Copyright (C) 2005-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
import psycopg2.extras
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dsn:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
curs.execute("SELECT 1 AS foo, 'cip' AS bar, date(now()) as zot")
print "Cursor's row factory is", curs.row_factory
data = curs.fetchone()
print "The type of the data row is", type(data)
print "Some data accessed both as tuple and dict:"
print " ", data['foo'], data['bar'], data['zot']
print " ", data[0], data[1], data[2]
# execute another query and demostrate we can still access the row
curs.execute("SELECT 2 AS foo")
print "The type of the data row is", type(data)
print "Some more data accessed both as tuple and dict:"
print " ", data['foo'], data['bar'], data['zot']
print " ", data[0], data[1], data[2]
curs = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
curs.execute("SELECT 1 AS foo, 'cip' AS bar, date(now()) as zot")
print "Cursor's row factory is", curs.row_factory
data = curs.fetchone()
print "The type of the data row is", type(data)
print "Some data accessed both as tuple and dict:"
print " ", data['foo'], data['bar'], data['zot']
print " ", "No access using indices: this is a specialized cursor."
# execute another query and demostrate we can still access the row
curs.execute("SELECT 2 AS foo")
print "The type of the data row is", type(data)
print "Some more data accessed both as tuple and dict:"
print " ", data['foo'], data['bar'], data['zot']
print " ", "No access using indices: this is a specialized cursor."

99
examples/dt.py Normal file
View File

@ -0,0 +1,99 @@
# datetime.py - example of using date and time types
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
import mx.DateTime
import datetime
from psycopg2.extensions import adapt
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
curs = conn.cursor()
try:
curs.execute("""CREATE TABLE test_dt (
k int4, d date, t time, dt timestamp, z interval)""")
except:
conn.rollback()
curs.execute("DROP TABLE test_dt")
curs.execute("""CREATE TABLE test_dt (
k int4, d date, t time, dt timestamp, z interval)""")
conn.commit()
# build and insert some data using mx.DateTime
mx1 = (
1,
mx.DateTime.Date(2004, 10, 19),
mx.DateTime.Time(0, 11, 17.015),
mx.DateTime.Timestamp(2004, 10, 19, 0, 11, 17.5),
mx.DateTime.DateTimeDelta(13, 15, 17, 59.9))
from psycopg2.extensions import adapt
import psycopg2.extras
print adapt(mx1)
print "Inserting mx.DateTime values..."
curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", mx1)
# build and insert some values using the datetime adapters
dt1 = (
2,
datetime.date(2004, 10, 19),
datetime.time(0, 11, 17, 15000),
datetime.datetime(2004, 10, 19, 0, 11, 17, 500000),
datetime.timedelta(13, 15*3600+17*60+59, 900000))
print "Inserting Python datetime values..."
curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", dt1)
# now extract the row from database and print them
print "Extracting values inserted with mx.DateTime wrappers:"
curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 1")
for n, x in zip(mx1[1:], curs.fetchone()):
try:
# this will work only if psycopg has been compiled with datetime
# as the default typecaster for date/time values
s = repr(n) + "\n -> " + str(adapt(n)) + \
"\n -> " + repr(x) + "\n -> " + x.isoformat()
except:
s = repr(n) + "\n -> " + str(adapt(n)) + \
"\n -> " + repr(x) + "\n -> " + str(x)
print s
print
print "Extracting values inserted with Python datetime wrappers:"
curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 2")
for n, x in zip(dt1[1:], curs.fetchone()):
try:
# this will work only if psycopg has been compiled with datetime
# as the default typecaster for date/time values
s = repr(n) + "\n -> " + repr(x) + "\n -> " + x.isoformat()
except:
s = repr(n) + "\n -> " + repr(x) + "\n -> " + str(x)
print s
print
curs.execute("DROP TABLE test_dt")
conn.commit()

105
examples/encoding.py Normal file
View File

@ -0,0 +1,105 @@
# encoding.py - show to change client enkoding (and test it works)
# -*- encoding: utf8 -*-
#
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
import psycopg2.extensions
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Initial encoding for this connection is", conn.encoding
print "\n** This example is supposed to be run in a UNICODE terminal! **\n"
print "Available encodings:"
encs = psycopg2.extensions.encodings.items()
encs.sort()
for a, b in encs:
print " ", a, "<->", b
print "Using STRING typecaster"
print "Setting backend encoding to LATIN1 and executing queries:"
conn.set_client_encoding('LATIN1')
curs = conn.cursor()
curs.execute("SELECT %s::TEXT AS foo", ('àèìòù',))
x = curs.fetchone()[0]
print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x)
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
x = curs.fetchone()[0]
print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x)
print "Setting backend encoding to UTF8 and executing queries:"
conn.set_client_encoding('UNICODE')
curs = conn.cursor()
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),))
x = curs.fetchone()[0]
print " ->", x, type(x)
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
x = curs.fetchone()[0]
print " ->", x, type(x)
print "Using UNICODE typecaster"
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
print "Setting backend encoding to LATIN1 and executing queries:"
conn.set_client_encoding('LATIN1')
curs = conn.cursor()
curs.execute("SELECT %s::TEXT AS foo", ('àèìòù',))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
print "Setting backend encoding to UTF8 and executing queries:"
conn.set_client_encoding('UNICODE')
curs = conn.cursor()
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
curs.execute("SELECT %s::TEXT AS foo", (u'àèìòù',))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
print "Executing full UNICODE queries"
print "Setting backend encoding to LATIN1 and executing queries:"
conn.set_client_encoding('LATIN1')
curs = conn.cursor()
curs.execute(u"SELECT %s::TEXT AS foo", ('àèìòù',))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù',))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
print "Setting backend encoding to UTF8 and executing queries:"
conn.set_client_encoding('UNICODE')
curs = conn.cursor()
curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù'.encode('utf-8'),))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)
curs.execute(u"SELECT %s::TEXT AS foo", (u'àèìòù',))
x = curs.fetchone()[0]
print " ->", x.encode('utf-8'), ":", type(x)

80
examples/fetch.py Normal file
View File

@ -0,0 +1,80 @@
# fetch.py -- example about declaring cursors
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_fetch (val int4)")
except:
conn.rollback()
curs.execute("DROP TABLE test_fetch")
curs.execute("CREATE TABLE test_fetch (val int4)")
conn.commit()
# we use this function to format the output
def flatten(l):
"""Flattens list of tuples l."""
return map(lambda x: x[0], l)
# insert 20 rows in the table
for i in range(20):
curs.execute("INSERT INTO test_fetch VALUES(%s)", (i,))
conn.commit()
# does some nice tricks with the transaction and postgres cursors
# (remember to always commit or rollback before a DECLARE)
#
# we don't need to DECLARE ourselves, psycopg now supports named
# cursors (but we leave the code here, comments, as an example of
# what psycopg is doing under the hood)
#
#curs.execute("DECLARE crs CURSOR FOR SELECT * FROM test_fetch")
#curs.execute("FETCH 10 FROM crs")
#print "First 10 rows:", flatten(curs.fetchall())
#curs.execute("MOVE -5 FROM crs")
#print "Moved back cursor by 5 rows (to row 5.)"
#curs.execute("FETCH 10 FROM crs")
#print "Another 10 rows:", flatten(curs.fetchall())
#curs.execute("FETCH 10 FROM crs")
#print "The remaining rows:", flatten(curs.fetchall())
ncurs = conn.cursor("crs")
ncurs.execute("SELECT * FROM test_fetch")
print "First 10 rows:", flatten(ncurs.fetchmany(10))
ncurs.scroll(-5)
print "Moved back cursor by 5 rows (to row 5.)"
print "Another 10 rows:", flatten(ncurs.fetchmany(10))
print "Another one:", list(ncurs.fetchone())
print "The remaining rows:", flatten(ncurs.fetchall())
conn.rollback()
curs.execute("DROP TABLE test_fetch")
conn.commit()

59
examples/lastrowid.py Normal file
View File

@ -0,0 +1,59 @@
# lastrowid.py - example of using .lastrowid attribute
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys, psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_oid (name text, surname text)")
except:
conn.rollback()
curs.execute("DROP TABLE test_oid")
curs.execute("CREATE TABLE test_oid (name text, surname text)")
conn.commit()
data = ({'name':'Federico', 'surname':'Di Gregorio'},
{'name':'Pierluigi', 'surname':'Di Nunzio'})
curs.execute("""INSERT INTO test_oid
VALUES (%(name)s, %(surname)s)""", data[0])
foid = curs.lastrowid
print "Oid for %(name)s %(surname)s" % data[0], "is", foid
curs.execute("""INSERT INTO test_oid
VALUES (%(name)s, %(surname)s)""", data[1])
moid = curs.lastrowid
print "Oid for %(name)s %(surname)s" % data[1], "is", moid
curs.execute("SELECT * FROM test_oid WHERE oid = %s", (foid,))
print "Oid", foid, "selected %s %s" % curs.fetchone()
curs.execute("SELECT * FROM test_oid WHERE oid = %s", (moid,))
print "Oid", moid, "selected %s %s" % curs.fetchone()
curs.execute("DROP TABLE test_oid")
conn.commit()

91
examples/lobject.py Normal file
View File

@ -0,0 +1,91 @@
# lobject.py - lobject example
#
# Copyright (C) 2001-2006 Federico Di Gregorio <fog@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
# this will create a large object with a new random oid, we'll
# use it to make some basic tests about read/write and seek.
lobj = conn.lobject()
loid = lobj.oid
print "Created a new large object with oid", loid
print "Manually importing some binary data into the object:"
data = open("somehackers.jpg").read()
len = lobj.write(data)
print " imported", len, "bytes of data"
conn.commit()
print "Trying to (re)open large object with oid", loid
lobj = conn.lobject(loid)
print "Manually exporting the data from the lobject:"
data1 = lobj.read()
len = lobj.tell()
lobj.seek(0, 0)
data2 = lobj.read()
if data1 != data2:
print "ERROR: read after seek returned different data"
open("somehackers_lobject1.jpg", 'wb').write(data1)
print " written", len, "bytes of data to somehackers_lobject1.jpg"
lobj.unlink()
print "Large object with oid", loid, "removed"
conn.commit()
# now we try to use the import and export functions to do the same
lobj = conn.lobject(0, 'n', 0, "somehackers.jpg")
loid = lobj.oid
print "Imported a new large object with oid", loid
conn.commit()
print "Trying to (re)open large object with oid", loid
lobj = conn.lobject(loid, 'n')
print "Using export() to export the data from the large object:"
lobj.export("somehackers_lobject2.jpg")
print " exported large object to somehackers_lobject2.jpg"
lobj.unlink()
print "Large object with oid", loid, "removed"
conn.commit()
# this will create a very large object with a new random oid.
lobj = conn.lobject()
loid = lobj.oid
print "Created a new large object with oid", loid
print "Manually importing a lot of data into the object:"
data = "data" * 1000000
len = lobj.write(data)
print " imported", len, "bytes of data"
conn.rollback()
print "\nNow try to load the new images, to check it worked!"

47
examples/mogrify.py Normal file
View File

@ -0,0 +1,47 @@
# mogrify.py - test all possible simple type mogrifications
# -*- encoding: latin1 -*-
#
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details..
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys, psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'})
curs.execute("SELECT %(foo)s AS foo", {'foo':None})
curs.execute("SELECT %(foo)s AS foo", {'foo':True})
curs.execute("SELECT %(foo)s AS foo", {'foo':42})
curs.execute("SELECT %(foo)s AS foo", {'foo':u'yatt<EFBFBD>!'})
curs.execute("SELECT %(foo)s AS foo", {'foo':u'bar'})
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':'bar'})
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':None})
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':True})
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':42})
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'yatt<EFBFBD>!'})
print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'bar'})
conn.rollback()

126
examples/myfirstrecipe.py Normal file
View File

@ -0,0 +1,126 @@
"""
Using a tuple as a bound variable in "SELECT ... IN (...)" clauses
in PostgreSQL using psycopg2
Some time ago someone asked on the psycopg mailing list how to have a
bound variable expand to the right SQL for an SELECT IN clause:
SELECT * FROM atable WHERE afield IN (value1, value2, value3)
with the values to be used in the IN clause to be passed to the cursor
.execute() method in a tuple as a bound variable, i.e.:
in_values = ("value1", "value2", "value3")
curs.execute("SELECT ... IN %s", (in_values,))
psycopg 1 does support typecasting from Python to PostgreSQL (and back)
only for simple types and this problem has no elegant solution (short or
writing a wrapper class returning the pre-quoted text in an __str__
method.
But psycopg2 offers a simple and elegant solution by partially
implementing the Object Adaptation from PEP 246. psycopg2 moves
the type-casting logic into external adapters and a somehow
broken adapt() function.
While the original adapt() takes 3 arguments, psycopg2's one only takes
1: the bound variable to be adapted. The result is an object supporting
a not-yet well defined protocol that we can call ISQLQuote:
class ISQLQuote:
def getquoted(self):
"Returns a quoted string representing the bound variable."
def getbinary(self):
"Returns a binary quoted string representing the bound variable."
def getbuffer(self):
"Returns the wrapped object itself."
__str__ = getquoted
Then one of the functions (usually .getquoted()) is called by psycopg2 at
the right time to obtain the right, sql-quoted representation for the
corresponding bound variable.
The nice part is that the default, built-in adapters, derived from
psycopg 1 tyecasting code can be overridden by the programmer, simply
replacing them in the psycopg.extensions.adapters dictionary.
Then the solution to the original problem is now obvious: write an
adapter that adapts tuple objects into the right SQL string, by calling
recursively adapt() on each element.
psycopg2 development can be tracked on the psycopg mailing list:
http://lists.initd.org/mailman/listinfo/psycopg
"""
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import psycopg2
import psycopg2.extensions
from psycopg2.extensions import adapt as psycoadapt
from psycopg2.extensions import register_adapter
class AsIs(object):
"""An adapter that just return the object 'as is'.
psycopg 1.99.9 has some optimizations that make impossible to call
adapt() without adding some basic adapters externally. This limitation
will be lifted in a future release.
"""
def __init__(self, obj):
self.__obj = obj
def getquoted(self):
return self.__obj
class SQL_IN(object):
"""Adapt a tuple to an SQL quotable object."""
def __init__(self, seq):
self._seq = seq
def prepare(self, conn):
pass
def getquoted(self):
# this is the important line: note how every object in the
# list is adapted and then how getquoted() is called on it
qobjs = [str(psycoadapt(o).getquoted()) for o in self._seq]
return '(' + ', '.join(qobjs) + ')'
__str__ = getquoted
# add our new adapter class to psycopg list of adapters
register_adapter(tuple, SQL_IN)
register_adapter(float, AsIs)
register_adapter(int, AsIs)
# usually we would call:
#
# conn = psycopg.connect("...")
# curs = conn.cursor()
# curs.execute("SELECT ...", (("this", "is", "the", "tuple"),))
#
# but we have no connection to a database right now, so we just check
# the SQL_IN class by calling psycopg's adapt() directly:
if __name__ == '__main__':
print "Note how the string will be SQL-quoted, but the number will not:"
print psycoadapt(("this is an 'sql quoted' str\\ing", 1, 2.0))

45
examples/notify.py Normal file
View File

@ -0,0 +1,45 @@
# notify.py - example of getting notifies
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import select
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
curs = conn.cursor()
curs.execute("listen test")
print "Waiting for 'NOTIFY test'"
while 1:
if select.select([conn],[],[],5)==([],[],[]):
print "Timeout"
else:
conn.poll()
while conn.notifies:
print "Got NOTIFY:", conn.notifies.pop()

54
examples/simple.py Normal file
View File

@ -0,0 +1,54 @@
# simple.py - very simple example of plain DBAPI-2.0 usage
#
# currently used as test-me-stress-me script for psycopg 2.0
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
class SimpleQuoter(object):
def sqlquote(x=None):
return "'bar'"
import sys
import psycopg2
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
curs.execute("SELECT 1 AS foo")
print curs.fetchone()
curs.execute("SELECT 1 AS foo")
print curs.fetchmany()
curs.execute("SELECT 1 AS foo")
print curs.fetchall()
conn.rollback()
sys.exit(0)
curs.execute("SELECT 1 AS foo", async=1)
curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'})
curs.execute("SELECT %(foo)s AS foo", {'foo':None})
curs.execute("SELECT %(foo)f AS foo", {'foo':42})
curs.execute("SELECT %(foo)s AS foo", {'foo':SimpleQuoter()})

BIN
examples/somehackers.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

161
examples/threads.py Normal file
View File

@ -0,0 +1,161 @@
# threads.py -- example of multiple threads using psycopg
# -*- encoding: latin1 -*-
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## some others parameters
INSERT_THREADS = ('A', 'B', 'C')
SELECT_THREADS = ('1', '2')
ROWS = 1000
COMMIT_STEP = 20
SELECT_SIZE = 10000
SELECT_STEP = 500
SELECT_DIV = 250
# the available modes are:
# 0 - one connection for all inserts and one for all select threads
# 1 - connections generated using the connection pool
MODE = 1
## don't modify anything below this line (except for experimenting)
import sys, psycopg2, threading
from psycopg2.pool import ThreadedConnectionPool
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
if len(sys.argv) > 1:
DSN = sys.argv[1]
if len(sys.argv) > 2:
MODE = int(sys.argv[2])
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
curs = conn.cursor()
try:
curs.execute("""CREATE TABLE test_threads (
name text, value1 int4, value2 float)""")
except:
conn.rollback()
curs.execute("DROP TABLE test_threads")
curs.execute("""CREATE TABLE test_threads (
name text, value1 int4, value2 float)""")
conn.commit()
## this function inserts a big number of rows and creates and destroys
## a large number of cursors
def insert_func(conn_or_pool, rows):
name = threading.currentThread().getName()
if MODE == 0:
conn = conn_or_pool
else:
conn = conn_or_pool.getconn()
for i in range(rows):
if divmod(i, COMMIT_STEP)[1] == 0:
conn.commit()
if MODE == 1:
conn_or_pool.putconn(conn)
s = name + ": COMMIT STEP " + str(i)
print s
if MODE == 1:
conn = conn_or_pool.getconn()
c = conn.cursor()
try:
c.execute("INSERT INTO test_threads VALUES (%s, %s, %s)",
(str(i), i, float(i)))
except psycopg2.ProgrammingError, err:
print name, ": an error occurred; skipping this insert"
print err
conn.commit()
## a nice select function that prints the current number of rows in the
## database (and transfer them, putting some pressure on the network)
def select_func(conn_or_pool, z):
name = threading.currentThread().getName()
if MODE == 0:
conn = conn_or_pool
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
for i in range(SELECT_SIZE):
if divmod(i, SELECT_STEP)[1] == 0:
try:
if MODE == 1:
conn = conn_or_pool.getconn()
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
c = conn.cursor()
c.execute("SELECT * FROM test_threads WHERE value2 < %s",
(int(i/z),))
l = c.fetchall()
if MODE == 1:
conn_or_pool.putconn(conn)
s = name + ": number of rows fetched: " + str(len(l))
print s
except psycopg2.ProgrammingError, err:
print name, ": an error occurred; skipping this select"
print err
## create the connection pool or the connections
if MODE == 0:
conn_insert = psycopg2.connect(DSN)
conn_select = psycopg2.connect(DSN)
else:
m = len(INSERT_THREADS) + len(SELECT_THREADS)
n = m/2
conn_insert = conn_select = ThreadedConnectionPool(n, m, DSN)
## create the threads
threads = []
print "Creating INSERT threads:"
for name in INSERT_THREADS:
t = threading.Thread(None, insert_func, 'Thread-'+name,
(conn_insert, ROWS))
t.setDaemon(0)
threads.append(t)
print "Creating SELECT threads:"
for name in SELECT_THREADS:
t = threading.Thread(None, select_func, 'Thread-'+name,
(conn_select, SELECT_DIV))
t.setDaemon(0)
threads.append(t)
## really start the threads now
for t in threads:
t.start()
# and wait for them to finish
for t in threads:
t.join()
print t.getName(), "exited OK"
conn.commit()
curs.execute("SELECT count(name) FROM test_threads")
print "Inserted", curs.fetchone()[0], "rows."
curs.execute("DROP TABLE test_threads")
conn.commit()

67
examples/typecast.py Normal file
View File

@ -0,0 +1,67 @@
# typecast.py - example of per-cursor and per-connection typecasters.
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
class SimpleQuoter(object):
def sqlquote(x=None):
return "'bar'"
import sys
import psycopg2
import psycopg2.extensions
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Encoding for this connection is", conn.encoding
curs = conn.cursor()
curs.execute("SELECT 'text'::text AS foo")
textoid = curs.description[0][1]
print "Oid for the text datatype is", textoid
def castA(s, curs):
if s is not None: return "(A) " + s
TYPEA = psycopg2.extensions.new_type((textoid,), "TYPEA", castA)
def castB(s, curs):
if s is not None: return "(B) " + s
TYPEB = psycopg2.extensions.new_type((textoid,), "TYPEB", castB)
curs = conn.cursor()
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from plain connection:", curs.fetchone()[0]
psycopg2.extensions.register_type(TYPEA, conn)
curs = conn.cursor()
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from connection with typecaster:", curs.fetchone()[0]
curs = conn.cursor()
psycopg2.extensions.register_type(TYPEB, curs)
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from cursor with typecaster:", curs.fetchone()[0]
curs = conn.cursor()
curs.execute("SELECT 'some text.'::text AS foo")
print "Some text from connection with typecaster again:", curs.fetchone()[0]

69
examples/tz.py Normal file
View File

@ -0,0 +1,69 @@
# tz.py - example of datetime objects with time zones
# -*- encoding: utf8 -*-
#
# Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
import datetime
from psycopg2.tz import ZERO, LOCAL, FixedOffsetTimezone
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_tz (t timestamp with time zone)")
except:
conn.rollback()
curs.execute("DROP TABLE test_tz")
curs.execute("CREATE TABLE test_tz (t timestamp with time zone)")
conn.commit()
d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=LOCAL)
curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
print "Inserted timestamp with timezone:", d
print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
tz = FixedOffsetTimezone(-5*60, "EST")
d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=tz)
curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
print "Inserted timestamp with timezone:", d
print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
curs.execute("SELECT * FROM test_tz")
d = curs.fetchone()[0]
curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
print "Inserted SELECTed timestamp:", d
print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
curs.execute("SELECT * FROM test_tz")
for d in curs:
u = d[0].utcoffset() or ZERO
print "UTC time: ", d[0] - u
print "Local time:", d[0]
print "Time zone:", d[0].tzinfo.tzname(d[0]), d[0].tzinfo.utcoffset(d[0])
curs.execute("DROP TABLE test_tz")
conn.commit()

126
examples/usercast.py Normal file
View File

@ -0,0 +1,126 @@
# usercast.py -- example of user defined typecasters
# -*- encoding: latin-1 -*-
#
# Copyright (C) 2001-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
## put in DSN your DSN string
DSN = 'dbname=test'
## don't modify anything below this line (except for experimenting)
import sys
import psycopg2
import psycopg2.extensions
import whrandom
# importing psycopg.extras will give us a nice tuple adapter: this is wrong
# because the adapter is meant to be used in SQL IN clauses while we use
# tuples to represent points but it works and the example is about Rect, not
# "Point"
import psycopg2.extras
if len(sys.argv) > 1:
DSN = sys.argv[1]
print "Opening connection using dns:", DSN
conn = psycopg2.connect(DSN)
print "Initial encoding for this connection is", conn.encoding
curs = conn.cursor()
try:
curs.execute("CREATE TABLE test_cast (p1 point, p2 point, b box)")
except:
conn.rollback()
curs.execute("DROP TABLE test_cast")
curs.execute("CREATE TABLE test_cast (p1 point, p2 point, b box)")
conn.commit()
# this is the callable object we use as a typecast (the typecast is
# usually a function, but we use a class, just to demonstrate the
# flexibility of the psycopg casting system
class Rect(object):
"""Very simple rectangle.
Note that we use this type as a data holder, as an adapter of itself for
the ISQLQuote protocol used by psycopg's adapt() (see __confrom__ below)
and eventually as a type-caster for the data extracted from the database
(that's why __init__ takes the curs argument.)
"""
def __init__(self, s=None, curs=None):
"""Init the rectangle from the optional string s."""
self.x = self.y = self.width = self.height = 0.0
if s: self.from_string(s)
def __conform__(self, proto):
"""This is a terrible hack, just ignore proto and return self."""
if proto == psycopg2.extensions.ISQLQuote:
return self
def from_points(self, x0, y0, x1, y1):
"""Init the rectangle from points."""
if x0 > x1: (x0, x1) = (x1, x0)
if y0 > y1: (y0, y1) = (y1, y0)
self.x = x0
self.y = y0
self.width = x1 - x0
self.height = y1 - y0
def from_string(self, s):
"""Init the rectangle from a string."""
seq = eval(s)
self.from_points(seq[0][0], seq[0][1], seq[1][0], seq[1][1])
def getquoted(self):
"""Format self as a string usable by the db to represent a box."""
s = "'((%d,%d),(%d,%d))'" % (
self.x, self.y, self.x + self.width, self.y + self.height)
return s
def show(self):
"""Format a description of the box."""
s = "X: %d\tY: %d\tWidth: %d\tHeight: %d" % (
self.x, self.y, self.width, self.height)
return s
# here we select from the empty table, just to grab the description
curs.execute("SELECT b FROM test_cast WHERE 0=1")
boxoid = curs.description[0][1]
print "Oid for the box datatype is", boxoid
# and build the user cast object
BOX = psycopg2.extensions.new_type((boxoid,), "BOX", Rect)
psycopg2.extensions.register_type(BOX)
# now insert 100 random data (2 points and a box in each row)
for i in range(100):
p1 = (whrandom.randint(0,100), whrandom.randint(0,100))
p2 = (whrandom.randint(0,100), whrandom.randint(0,100))
b = Rect()
b.from_points(whrandom.randint(0,100), whrandom.randint(0,100),
whrandom.randint(0,100), whrandom.randint(0,100))
curs.execute("INSERT INTO test_cast VALUES ('%(p1)s', '%(p2)s', %(box)s)",
{'box':b, 'p1':p1, 'p2':p2})
print "Added 100 boxed to the database"
# select and print all boxes with at least one point inside
curs.execute("SELECT b FROM test_cast WHERE p1 @ b OR p2 @ b")
boxes = curs.fetchall()
print "Found %d boxes with at least a point inside:" % len(boxes)
for box in boxes:
print " ", box[0].show()
curs.execute("DROP TABLE test_cast")
conn.commit()

BIN
examples/whereareyou.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -6,10 +6,10 @@ provide new-style classes for connection and cursor objects and other sweet
candies. Like the original, psycopg 2 was written with the aim of being very candies. Like the original, psycopg 2 was written with the aim of being very
small and fast, and stable as a rock. small and fast, and stable as a rock.
Homepage: https://psycopg.org/ Homepage: http://initd.org/projects/psycopg2
.. _PostgreSQL: https://www.postgresql.org/ .. _PostgreSQL: http://www.postgresql.org/
.. _Python: https://www.python.org/ .. _Python: http://www.python.org/
:Groups: :Groups:
* `Connections creation`: connect * `Connections creation`: connect
@ -18,8 +18,7 @@ Homepage: https://psycopg.org/
""" """
# psycopg/__init__.py - initialization of the psycopg module # psycopg/__init__.py - initialization of the psycopg module
# #
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> # Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -44,44 +43,69 @@ Homepage: https://psycopg.org/
# Note: the first internal import should be _psycopg, otherwise the real cause # Note: the first internal import should be _psycopg, otherwise the real cause
# of a failed loading of the C module may get hidden, see # of a failed loading of the C module may get hidden, see
# https://archives.postgresql.org/psycopg/2011-02/msg00044.php # http://archives.postgresql.org/psycopg/2011-02/msg00044.php
# Import the DBAPI-2.0 stuff into top-level module. # Import the DBAPI-2.0 stuff into top-level module.
from psycopg2._psycopg import ( # noqa from psycopg2._psycopg import BINARY, NUMBER, STRING, DATETIME, ROWID
BINARY, NUMBER, STRING, DATETIME, ROWID,
Binary, Date, Time, Timestamp, from psycopg2._psycopg import Binary, Date, Time, Timestamp
DateFromTicks, TimeFromTicks, TimestampFromTicks, from psycopg2._psycopg import DateFromTicks, TimeFromTicks, TimestampFromTicks
Error, Warning, DataError, DatabaseError, ProgrammingError, IntegrityError, from psycopg2._psycopg import Error, Warning, DataError, DatabaseError, ProgrammingError
InterfaceError, InternalError, NotSupportedError, OperationalError, from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError
from psycopg2._psycopg import NotSupportedError, OperationalError
_connect, apilevel, threadsafety, paramstyle, from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle
__version__, __libpq_version__, from psycopg2._psycopg import __version__
)
from psycopg2 import tz
# Register default adapters. # Register default adapters.
from psycopg2 import extensions as _ext import psycopg2.extensions as _ext
_ext.register_adapter(tuple, _ext.SQL_IN) _ext.register_adapter(tuple, _ext.SQL_IN)
_ext.register_adapter(type(None), _ext.NoneAdapter) _ext.register_adapter(type(None), _ext.NoneAdapter)
# Register the Decimal adapter here instead of in the C layer. # Register the Decimal adapter here instead of in the C layer.
# This way a new class is registered for each sub-interpreter. # This way a new class is registered for each sub-interpreter.
# See ticket #52 # See ticket #52
from decimal import Decimal # noqa try:
from psycopg2._psycopg import Decimal as Adapter # noqa from decimal import Decimal
_ext.register_adapter(Decimal, Adapter) except ImportError:
del Decimal, Adapter pass
else:
from psycopg2._psycopg import Decimal as Adapter
_ext.register_adapter(Decimal, Adapter)
del Decimal, Adapter
import re
def _param_escape(s,
re_escape=re.compile(r"([\\'])"),
re_space=re.compile(r'\s')):
"""
Apply the escaping rule required by PQconnectdb
"""
if not s: return "''"
s = re_escape.sub(r'\\\1', s)
if re_space.search(s):
s = "'" + s + "'"
return s
del re
def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs): def connect(dsn=None,
database=None, user=None, password=None, host=None, port=None,
connection_factory=None, cursor_factory=None, async=False, **kwargs):
""" """
Create a new database connection. Create a new database connection.
The connection parameters can be specified as a string: The connection parameters can be specified either as a string:
conn = psycopg2.connect("dbname=test user=postgres password=secret") conn = psycopg2.connect("dbname=test user=postgres password=secret")
@ -89,9 +113,9 @@ def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs):
conn = psycopg2.connect(database="test", user="postgres", password="secret") conn = psycopg2.connect(database="test", user="postgres", password="secret")
Or as a mix of both. The basic connection parameters are: The basic connection parameters are:
- *dbname*: the database name - *dbname*: the database name (only in dsn string)
- *database*: the database name (only as keyword argument) - *database*: the database name (only as keyword argument)
- *user*: user name used to authenticate - *user*: user name used to authenticate
- *password*: password used to authenticate - *password*: password used to authenticate
@ -105,21 +129,39 @@ def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs):
Using the *cursor_factory* parameter, a new default cursor factory will be Using the *cursor_factory* parameter, a new default cursor factory will be
used by cursor(). used by cursor().
Using *async*=True an asynchronous connection will be created. *async_* is Using *async*=True an asynchronous connection will be created.
a valid alias (for Python versions where ``async`` is a keyword).
Any other keyword parameter will be passed to the underlying client Any other keyword parameter will be passed to the underlying client
library: the list of supported parameters depends on the library version. library: the list of supported parameters depends on the library version.
""" """
kwasync = {} items = []
if 'async' in kwargs: if database is not None:
kwasync['async'] = kwargs.pop('async') items.append(('dbname', database))
if 'async_' in kwargs: if user is not None:
kwasync['async_'] = kwargs.pop('async_') items.append(('user', user))
if password is not None:
items.append(('password', password))
if host is not None:
items.append(('host', host))
if port is not None:
items.append(('port', port))
dsn = _ext.make_dsn(dsn, **kwargs) items.extend([(k, v) for (k, v) in kwargs.iteritems() if v is not None])
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
if dsn is not None and items:
raise TypeError(
"'%s' is an invalid keyword argument when the dsn is specified"
% items[0][0])
if dsn is None:
if not items:
raise TypeError('missing dsn and no parameters')
else:
dsn = " ".join(["%s=%s" % (k, _param_escape(str(v)))
for (k, v) in items])
conn = _connect(dsn, connection_factory=connection_factory, async=async)
if cursor_factory is not None: if cursor_factory is not None:
conn.cursor_factory = cursor_factory conn.cursor_factory = cursor_factory

View File

@ -1,90 +0,0 @@
"""Implementation of the ipaddres-based network types adaptation
"""
# psycopg/_ipaddress.py - Ipaddres-based network types adaptation
#
# Copyright (C) 2016-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020-2021 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
from psycopg2.extensions import (
new_type, new_array_type, register_type, register_adapter, QuotedString)
# The module is imported on register_ipaddress
ipaddress = None
# The typecasters are created only once
_casters = None
def register_ipaddress(conn_or_curs=None):
"""
Register conversion support between `ipaddress` objects and `network types`__.
:param conn_or_curs: the scope where to register the type casters.
If `!None` register them globally.
After the function is called, PostgreSQL :sql:`inet` values will be
converted into `~ipaddress.IPv4Interface` or `~ipaddress.IPv6Interface`
objects, :sql:`cidr` values into into `~ipaddress.IPv4Network` or
`~ipaddress.IPv6Network`.
.. __: https://www.postgresql.org/docs/current/static/datatype-net-types.html
"""
global ipaddress
import ipaddress
global _casters
if _casters is None:
_casters = _make_casters()
for c in _casters:
register_type(c, conn_or_curs)
for t in [ipaddress.IPv4Interface, ipaddress.IPv6Interface,
ipaddress.IPv4Network, ipaddress.IPv6Network]:
register_adapter(t, adapt_ipaddress)
def _make_casters():
inet = new_type((869,), 'INET', cast_interface)
ainet = new_array_type((1041,), 'INET[]', inet)
cidr = new_type((650,), 'CIDR', cast_network)
acidr = new_array_type((651,), 'CIDR[]', cidr)
return [inet, ainet, cidr, acidr]
def cast_interface(s, cur=None):
if s is None:
return None
# Py2 version force the use of unicode. meh.
return ipaddress.ip_interface(str(s))
def cast_network(s, cur=None):
if s is None:
return None
return ipaddress.ip_network(str(s))
def adapt_ipaddress(obj):
return QuotedString(str(obj))

View File

@ -7,8 +7,7 @@ extensions importing register_json from extras.
# psycopg/_json.py - Implementation of the JSON adaptation objects # psycopg/_json.py - Implementation of the JSON adaptation objects
# #
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com> # Copyright (C) 2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -28,35 +27,47 @@ extensions importing register_json from extras.
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details. # License for more details.
import json import sys
from psycopg2._psycopg import ISQLQuote, QuotedString from psycopg2._psycopg import ISQLQuote, QuotedString
from psycopg2._psycopg import new_type, new_array_type, register_type from psycopg2._psycopg import new_type, new_array_type, register_type
# import the best json implementation available
if sys.version_info[:2] >= (2,6):
import json
else:
try:
import simplejson as json
except ImportError:
json = None
# oids from PostgreSQL 9.2 # oids from PostgreSQL 9.2
JSON_OID = 114 JSON_OID = 114
JSONARRAY_OID = 199 JSONARRAY_OID = 199
# oids from PostgreSQL 9.4 class Json(object):
JSONB_OID = 3802
JSONBARRAY_OID = 3807
class Json:
""" """
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
:sql:`json` data type. :sql:`json` data type.
`!Json` can be used to wrap any object supported by the provided *dumps* `!Json` can be used to wrap any object supported by the provided *dumps*
function. If none is provided, the standard :py:func:`json.dumps()` is function. If none is provided, the standard :py:func:`json.dumps()` is
used. used (`!simplejson` for Python < 2.6;
`~psycopg2.extensions.ISQLQuote.getquoted()` will raise `!ImportError` if
the module is not available).
""" """
def __init__(self, adapted, dumps=None): def __init__(self, adapted, dumps=None):
self.adapted = adapted self.adapted = adapted
self._conn = None
self._dumps = dumps or json.dumps if dumps is not None:
self._dumps = dumps
elif json is not None:
self._dumps = json.dumps
else:
self._dumps = None
def __conform__(self, proto): def __conform__(self, proto):
if proto is ISQLQuote: if proto is ISQLQuote:
@ -69,25 +80,21 @@ class Json:
provided in the constructor. You can override this method to create a provided in the constructor. You can override this method to create a
customized JSON wrapper. customized JSON wrapper.
""" """
return self._dumps(obj) dumps = self._dumps
if dumps is not None:
def prepare(self, conn): return dumps(obj)
self._conn = conn else:
raise ImportError(
"json module not available: "
"you should provide a dumps function")
def getquoted(self): def getquoted(self):
s = self.dumps(self.adapted) s = self.dumps(self.adapted)
qs = QuotedString(s) return QuotedString(s).getquoted()
if self._conn is not None:
qs.prepare(self._conn)
return qs.getquoted()
def __str__(self):
# getquoted is binary
return self.getquoted().decode('ascii', 'replace')
def register_json(conn_or_curs=None, globally=False, loads=None, def register_json(conn_or_curs=None, globally=False, loads=None,
oid=None, array_oid=None, name='json'): oid=None, array_oid=None):
"""Create and register typecasters converting :sql:`json` type to Python objects. """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` :param conn_or_curs: a connection or cursor used to find the :sql:`json`
@ -103,19 +110,17 @@ def register_json(conn_or_curs=None, globally=False, loads=None,
queried on *conn_or_curs* queried on *conn_or_curs*
:param array_oid: the OID of the :sql:`json[]` array type if known; :param array_oid: the OID of the :sql:`json[]` array type if known;
if not, it will be queried on *conn_or_curs* 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 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 (or an alternative database and look for the OID of the :sql:`json` type. No query is
type if *name* if provided). No query is performed if *oid* and *array_oid* performed if *oid* and *array_oid* are provided. Raise
are provided. Raise `~psycopg2.ProgrammingError` if the type is not found. `~psycopg2.ProgrammingError` if the type is not found.
""" """
if oid is None: if oid is None:
oid, array_oid = _get_json_oids(conn_or_curs, name) oid, array_oid = _get_json_oids(conn_or_curs)
JSON, JSONARRAY = _create_json_typecasters( JSON, JSONARRAY = _create_json_typecasters(oid, array_oid, loads)
oid, array_oid, loads=loads, name=name.upper())
register_type(JSON, not globally and conn_or_curs or None) register_type(JSON, not globally and conn_or_curs or None)
@ -124,7 +129,6 @@ def register_json(conn_or_curs=None, globally=False, loads=None,
return JSON, JSONARRAY return JSON, JSONARRAY
def register_default_json(conn_or_curs=None, globally=False, loads=None): def register_default_json(conn_or_curs=None, globally=False, loads=None):
""" """
Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following. Create and register :sql:`json` typecasters for PostgreSQL 9.2 and following.
@ -137,40 +141,28 @@ def register_default_json(conn_or_curs=None, globally=False, loads=None):
return register_json(conn_or_curs=conn_or_curs, globally=globally, return register_json(conn_or_curs=conn_or_curs, globally=globally,
loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID) 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.""" """Create typecasters for json data type."""
if loads is None: if loads is None:
loads = json.loads if json is None:
raise ImportError("no json module available")
else:
loads = json.loads
def typecast_json(s, cur): def typecast_json(s, cur):
if s is None: if s is None:
return None return None
return loads(s) return loads(s)
JSON = new_type((oid, ), name, typecast_json) JSON = new_type((oid, ), 'JSON', typecast_json)
if array_oid is not None: if array_oid is not None:
JSONARRAY = new_array_type((array_oid, ), f"{name}ARRAY", JSON) JSONARRAY = new_array_type((array_oid, ), "JSONARRAY", JSON)
else: else:
JSONARRAY = None JSONARRAY = None
return JSON, JSONARRAY return JSON, JSONARRAY
def _get_json_oids(conn_or_curs):
def _get_json_oids(conn_or_curs, name='json'):
# lazy imports # lazy imports
from psycopg2.extensions import STATUS_IN_TRANSACTION from psycopg2.extensions import STATUS_IN_TRANSACTION
from psycopg2.extras import _solve_conn_curs from psycopg2.extras import _solve_conn_curs
@ -181,19 +173,22 @@ def _get_json_oids(conn_or_curs, name='json'):
conn_status = conn.status conn_status = conn.status
# column typarray not available before PG 8.3 # column typarray not available before PG 8.3
typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" typarray = conn.server_version >= 80300 and "typarray" or "NULL"
# get the oid for the hstore # get the oid for the hstore
curs.execute( curs.execute(
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = %%s;" "SELECT t.oid, %s FROM pg_type t WHERE t.typname = 'json';"
% typarray, (name,)) % typarray)
r = curs.fetchone() r = curs.fetchone()
# revert the status of the connection as before the command # revert the status of the connection as before the command
if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit: if (conn_status != STATUS_IN_TRANSACTION and not conn.autocommit):
conn.rollback() conn.rollback()
if not r: if not r:
raise conn.ProgrammingError(f"{name} data type not found") raise conn.ProgrammingError("json data type not found")
return r return r

View File

@ -4,8 +4,7 @@
# psycopg/_range.py - Implementation of the Range type and adaptation # psycopg/_range.py - Implementation of the Range type and adaptation
# #
# Copyright (C) 2012-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com> # Copyright (C) 2012 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -28,11 +27,10 @@
import re import re
from psycopg2._psycopg import ProgrammingError, InterfaceError from psycopg2._psycopg import ProgrammingError, InterfaceError
from psycopg2.extensions import ISQLQuote, adapt, register_adapter from psycopg2.extensions import ISQLQuote, adapt, register_adapter, b
from psycopg2.extensions import new_type, new_array_type, register_type from psycopg2.extensions import new_type, new_array_type, register_type
class Range(object):
class Range:
"""Python representation for a PostgreSQL |range|_ type. """Python representation for a PostgreSQL |range|_ type.
:param lower: lower bound for the range. `!None` means unbound :param lower: lower bound for the range. `!None` means unbound
@ -47,7 +45,7 @@ class Range:
def __init__(self, lower=None, upper=None, bounds='[)', empty=False): def __init__(self, lower=None, upper=None, bounds='[)', empty=False):
if not empty: if not empty:
if bounds not in ('[)', '(]', '()', '[]'): if bounds not in ('[)', '(]', '()', '[]'):
raise ValueError(f"bound flags not valid: {bounds!r}") raise ValueError("bound flags not valid: %r" % bounds)
self._lower = lower self._lower = lower
self._upper = upper self._upper = upper
@ -57,24 +55,11 @@ class Range:
def __repr__(self): def __repr__(self):
if self._bounds is None: if self._bounds is None:
return f"{self.__class__.__name__}(empty=True)" return "%s(empty=True)" % self.__class__.__name__
else: else:
return "{}({!r}, {!r}, {!r})".format(self.__class__.__name__, return "%s(%r, %r, %r)" % (self.__class__.__name__,
self._lower, self._upper, self._bounds) self._lower, self._upper, self._bounds)
def __str__(self):
if self._bounds is None:
return 'empty'
items = [
self._bounds[0],
str(self._lower),
', ',
str(self._upper),
self._bounds[1]
]
return ''.join(items)
@property @property
def lower(self): def lower(self):
"""The lower bound of the range. `!None` if empty or unbound.""" """The lower bound of the range. `!None` if empty or unbound."""
@ -93,59 +78,49 @@ class Range:
@property @property
def lower_inf(self): def lower_inf(self):
"""`!True` if the range doesn't have a lower bound.""" """`!True` if the range doesn't have a lower bound."""
if self._bounds is None: if self._bounds is None: return False
return False
return self._lower is None return self._lower is None
@property @property
def upper_inf(self): def upper_inf(self):
"""`!True` if the range doesn't have an upper bound.""" """`!True` if the range doesn't have an upper bound."""
if self._bounds is None: if self._bounds is None: return False
return False
return self._upper is None return self._upper is None
@property @property
def lower_inc(self): def lower_inc(self):
"""`!True` if the lower bound is included in the range.""" """`!True` if the lower bound is included in the range."""
if self._bounds is None or self._lower is None: if self._bounds is None: return False
return False if self._lower is None: return False
return self._bounds[0] == '[' return self._bounds[0] == '['
@property @property
def upper_inc(self): def upper_inc(self):
"""`!True` if the upper bound is included in the range.""" """`!True` if the upper bound is included in the range."""
if self._bounds is None or self._upper is None: if self._bounds is None: return False
return False if self._upper is None: return False
return self._bounds[1] == ']' return self._bounds[1] == ']'
def __contains__(self, x): def __contains__(self, x):
if self._bounds is None: if self._bounds is None: return False
return False
if self._lower is not None: if self._lower is not None:
if self._bounds[0] == '[': if self._bounds[0] == '[':
if x < self._lower: if x < self._lower: return False
return False
else: else:
if x <= self._lower: if x <= self._lower: return False
return False
if self._upper is not None: if self._upper is not None:
if self._bounds[1] == ']': if self._bounds[1] == ']':
if x > self._upper: if x > self._upper: return False
return False
else: else:
if x >= self._upper: if x >= self._upper: return False
return False
return True return True
def __bool__(self): def __nonzero__(self):
return self._bounds is not None return self._bounds is not None
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, Range):
return False
return (self._lower == other._lower return (self._lower == other._lower
and self._upper == other._upper and self._upper == other._upper
and self._bounds == other._bounds) and self._bounds == other._bounds)
@ -156,51 +131,12 @@ class Range:
def __hash__(self): def __hash__(self):
return hash((self._lower, self._upper, self._bounds)) return hash((self._lower, self._upper, self._bounds))
# as the postgres docs describe for the server-side stuff,
# ordering is rather arbitrary, but will remain stable
# and consistent.
def __lt__(self, other): def __lt__(self, other):
if not isinstance(other, Range): raise TypeError(
return NotImplemented 'Range objects cannot be ordered; please refer to the PostgreSQL'
for attr in ('_lower', '_upper', '_bounds'): ' documentation to perform this operation in the database')
self_value = getattr(self, attr)
other_value = getattr(other, attr)
if self_value == other_value:
pass
elif self_value is None:
return True
elif other_value is None:
return False
else:
return self_value < other_value
return False
def __le__(self, other): __le__ = __gt__ = __ge__ = __lt__
if self == other:
return True
else:
return self.__lt__(other)
def __gt__(self, other):
if isinstance(other, Range):
return other.__lt__(self)
else:
return NotImplemented
def __ge__(self, other):
if self == other:
return True
else:
return self.__gt__(other)
def __getstate__(self):
return {slot: getattr(self, slot)
for slot in self.__slots__ if hasattr(self, slot)}
def __setstate__(self, state):
for slot, value in state.items():
setattr(self, slot, value)
def register_range(pgrange, pyrange, conn_or_curs, globally=False): def register_range(pgrange, pyrange, conn_or_curs, globally=False):
@ -234,7 +170,7 @@ def register_range(pgrange, pyrange, conn_or_curs, globally=False):
return caster return caster
class RangeAdapter: class RangeAdapter(object):
"""`ISQLQuote` adapter for `Range` subclasses. """`ISQLQuote` adapter for `Range` subclasses.
This is an abstract class: concrete classes must set a `name` class This is an abstract class: concrete classes must set a `name` class
@ -260,7 +196,7 @@ class RangeAdapter:
r = self.adapted r = self.adapted
if r.isempty: if r.isempty:
return b"'empty'::" + self.name.encode('utf8') return b("'empty'::" + self.name)
if r.lower is not None: if r.lower is not None:
a = adapt(r.lower) a = adapt(r.lower)
@ -268,7 +204,7 @@ class RangeAdapter:
a.prepare(self._conn) a.prepare(self._conn)
lower = a.getquoted() lower = a.getquoted()
else: else:
lower = b'NULL' lower = b('NULL')
if r.upper is not None: if r.upper is not None:
a = adapt(r.upper) a = adapt(r.upper)
@ -276,13 +212,13 @@ class RangeAdapter:
a.prepare(self._conn) a.prepare(self._conn)
upper = a.getquoted() upper = a.getquoted()
else: else:
upper = b'NULL' upper = b('NULL')
return self.name.encode('utf8') + b'(' + lower + b', ' + upper \ return b(self.name + '(') + lower + b(', ') + upper \
+ b", '" + r._bounds.encode('utf8') + b"')" + b(", '%s')" % r._bounds)
class RangeCaster: class RangeCaster(object):
"""Helper class to convert between `Range` and PostgreSQL range types. """Helper class to convert between `Range` and PostgreSQL range types.
Objects of this class are usually created by `register_range()`. Manual Objects of this class are usually created by `register_range()`. Manual
@ -310,13 +246,12 @@ class RangeCaster:
# an implementation detail and is not documented. It is currently used # an implementation detail and is not documented. It is currently used
# for the numeric ranges. # for the numeric ranges.
self.adapter = None self.adapter = None
if isinstance(pgrange, str): if isinstance(pgrange, basestring):
self.adapter = type(pgrange, (RangeAdapter,), {}) self.adapter = type(pgrange, (RangeAdapter,), {})
self.adapter.name = pgrange self.adapter.name = pgrange
else: else:
try: try:
if issubclass(pgrange, RangeAdapter) \ if issubclass(pgrange, RangeAdapter) and pgrange is not RangeAdapter:
and pgrange is not RangeAdapter:
self.adapter = pgrange self.adapter = pgrange
except TypeError: except TypeError:
pass pass
@ -327,7 +262,7 @@ class RangeCaster:
self.range = None self.range = None
try: try:
if isinstance(pyrange, str): if isinstance(pyrange, basestring):
self.range = type(pyrange, (Range,), {}) self.range = type(pyrange, (Range,), {})
if issubclass(pyrange, Range) and pyrange is not Range: if issubclass(pyrange, Range) and pyrange is not Range:
self.range = pyrange self.range = pyrange
@ -348,9 +283,9 @@ class RangeCaster:
from psycopg2.extras import _solve_conn_curs from psycopg2.extras import _solve_conn_curs
conn, curs = _solve_conn_curs(conn_or_curs) conn, curs = _solve_conn_curs(conn_or_curs)
if conn.info.server_version < 90200: if conn.server_version < 90200:
raise ProgrammingError("range types not available in version %s" raise ProgrammingError("range types not available in version %s"
% conn.info.server_version) % conn.server_version)
# Store the transaction status of the connection to revert it after use # Store the transaction status of the connection to revert it after use
conn_status = conn.status conn_status = conn.status
@ -363,54 +298,33 @@ class RangeCaster:
schema = 'public' schema = 'public'
# get the type oid and attributes # get the type oid and attributes
curs.execute("""\ try:
select rngtypid, rngsubtype, typarray curs.execute("""\
select rngtypid, rngsubtype,
(select typarray from pg_type where oid = rngtypid)
from pg_range r from pg_range r
join pg_type t on t.oid = rngtypid join pg_type t on t.oid = rngtypid
join pg_namespace ns on ns.oid = typnamespace join pg_namespace ns on ns.oid = typnamespace
where typname = %s and ns.nspname = %s; where typname = %s and ns.nspname = %s;
""", (tname, schema)) """, (tname, schema))
rec = curs.fetchone()
if not rec: except ProgrammingError:
# The above algorithm doesn't work for customized seach_path if not conn.autocommit:
# (#1487) The implementation below works better, but, to guarantee conn.rollback()
# backwards compatibility, use it only if the original one failed. raise
try: else:
savepoint = False rec = curs.fetchone()
# Because we executed statements earlier, we are either INTRANS
# or we are IDLE only if the transaction is autocommit, in
# which case we don't need the savepoint anyway.
if conn.status == STATUS_IN_TRANSACTION:
curs.execute("SAVEPOINT register_type")
savepoint = True
curs.execute("""\ # revert the status of the connection as before the command
SELECT rngtypid, rngsubtype, typarray, typname, nspname if (conn_status != STATUS_IN_TRANSACTION
from pg_range r and not conn.autocommit):
join pg_type t on t.oid = rngtypid conn.rollback()
join pg_namespace ns on ns.oid = typnamespace
WHERE t.oid = %s::regtype
""", (name, ))
except ProgrammingError:
pass
else:
rec = curs.fetchone()
if rec:
tname, schema = rec[3:]
finally:
if savepoint:
curs.execute("ROLLBACK TO SAVEPOINT register_type")
# revert the status of the connection as before the command
if conn_status != STATUS_IN_TRANSACTION and not conn.autocommit:
conn.rollback()
if not rec: if not rec:
raise ProgrammingError( raise ProgrammingError(
f"PostgreSQL range '{name}' not found") "PostgreSQL type '%s' not found" % name)
type, subtype, array = rec[:3] type, subtype, array = rec
return RangeCaster(name, pyrange, return RangeCaster(name, pyrange,
oid=type, subtype_oid=subtype, array_oid=array) oid=type, subtype_oid=subtype, array_oid=array)
@ -440,7 +354,7 @@ WHERE t.oid = %s::regtype
m = self._re_range.match(s) m = self._re_range.match(s)
if m is None: if m is None:
raise InterfaceError(f"failed to parse range: '{s}'") raise InterfaceError("failed to parse range: %s")
lower = m.group(3) lower = m.group(3)
if lower is None: if lower is None:
@ -478,17 +392,14 @@ class NumericRange(Range):
""" """
pass pass
class DateRange(Range): class DateRange(Range):
"""Represents :sql:`daterange` values.""" """Represents :sql:`daterange` values."""
pass pass
class DateTimeRange(Range): class DateTimeRange(Range):
"""Represents :sql:`tsrange` values.""" """Represents :sql:`tsrange` values."""
pass pass
class DateTimeTZRange(Range): class DateTimeTZRange(Range):
"""Represents :sql:`tstzrange` values.""" """Represents :sql:`tstzrange` values."""
pass pass
@ -504,7 +415,7 @@ class NumberRangeAdapter(RangeAdapter):
def getquoted(self): def getquoted(self):
r = self.adapted r = self.adapted
if r.isempty: if r.isempty:
return b"'empty'" return "'empty'"
if not r.lower_inf: if not r.lower_inf:
# not exactly: we are relying that none of these object is really # not exactly: we are relying that none of these object is really
@ -520,12 +431,13 @@ class NumberRangeAdapter(RangeAdapter):
else: else:
upper = '' upper = ''
return (f"'{r._bounds[0]}{lower},{upper}{r._bounds[1]}'").encode('ascii') return b("'%s%s,%s%s'" % (
r._bounds[0], lower, upper, r._bounds[1]))
# TODO: probably won't work with infs, nans and other tricky cases. # TODO: probably won't work with infs, nans and other tricky cases.
register_adapter(NumericRange, NumberRangeAdapter) register_adapter(NumericRange, NumberRangeAdapter)
# Register globally typecasters and adapters for builtin range types. # Register globally typecasters and adapters for builtin range types.
# note: the adapter is registered more than once, but this is harmless. # note: the adapter is registered more than once, but this is harmless.
@ -552,3 +464,5 @@ tsrange_caster._register()
tstzrange_caster = RangeCaster('tstzrange', DateTimeTZRange, tstzrange_caster = RangeCaster('tstzrange', DateTimeTZRange,
oid=3910, subtype_oid=1184, array_oid=3911) oid=3910, subtype_oid=1184, array_oid=3911)
tstzrange_caster._register() tstzrange_caster._register()

View File

@ -1,11 +1,10 @@
"""Error codes for PostgreSQL """Error codes for PostgresSQL
This module contains symbolic names for all PostgreSQL error codes. This module contains symbolic names for all PostgreSQL error codes.
""" """
# psycopg2/errorcodes.py - PostgreSQL error codes # psycopg2/errorcodes.py - PostgreSQL error codes
# #
# Copyright (C) 2006-2019 Johan Dahlin <jdahlin@async.com.br> # Copyright (C) 2006-2010 Johan Dahlin <jdahlin@async.com.br>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -27,10 +26,9 @@ This module contains symbolic names for all PostgreSQL error codes.
# #
# Based on: # Based on:
# #
# https://www.postgresql.org/docs/current/static/errcodes-appendix.html # http://www.postgresql.org/docs/current/static/errcodes-appendix.html
# #
def lookup(code, _cache={}): def lookup(code, _cache={}):
"""Lookup an error code or class code and return its symbolic name. """Lookup an error code or class code and return its symbolic name.
@ -40,18 +38,11 @@ def lookup(code, _cache={}):
return _cache[code] return _cache[code]
# Generate the lookup map at first usage. # Generate the lookup map at first usage.
tmp = {} for k, v in globals().iteritems():
for k, v in globals().items():
if isinstance(v, str) and len(v) in (2, 5): if isinstance(v, str) and len(v) in (2, 5):
# Strip trailing underscore used to disambiguate duplicate values _cache[v] = k
tmp[v] = k.rstrip("_")
assert tmp return lookup(code)
# Atomic update, to avoid race condition on import (bug #382)
_cache.update(tmp)
return _cache[code]
# autogenerated data: do not edit below this point. # autogenerated data: do not edit below this point.
@ -95,7 +86,6 @@ CLASS_PROGRAM_LIMIT_EXCEEDED = '54'
CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55' CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55'
CLASS_OPERATOR_INTERVENTION = '57' CLASS_OPERATOR_INTERVENTION = '57'
CLASS_SYSTEM_ERROR = '58' CLASS_SYSTEM_ERROR = '58'
CLASS_SNAPSHOT_FAILURE = '72'
CLASS_CONFIGURATION_FILE_ERROR = 'F0' CLASS_CONFIGURATION_FILE_ERROR = 'F0'
CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV' CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV'
CLASS_PL_PGSQL_ERROR = 'P0' CLASS_PL_PGSQL_ERROR = 'P0'
@ -107,7 +97,7 @@ SUCCESSFUL_COMPLETION = '00000'
# Class 01 - Warning # Class 01 - Warning
WARNING = '01000' WARNING = '01000'
NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003' NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003'
STRING_DATA_RIGHT_TRUNCATION_ = '01004' STRING_DATA_RIGHT_TRUNCATION = '01004'
PRIVILEGE_NOT_REVOKED = '01006' PRIVILEGE_NOT_REVOKED = '01006'
PRIVILEGE_NOT_GRANTED = '01007' PRIVILEGE_NOT_GRANTED = '01007'
IMPLICIT_ZERO_BIT_PADDING = '01008' IMPLICIT_ZERO_BIT_PADDING = '01008'
@ -165,7 +155,7 @@ DATA_EXCEPTION = '22000'
STRING_DATA_RIGHT_TRUNCATION = '22001' STRING_DATA_RIGHT_TRUNCATION = '22001'
NULL_VALUE_NO_INDICATOR_PARAMETER = '22002' NULL_VALUE_NO_INDICATOR_PARAMETER = '22002'
NUMERIC_VALUE_OUT_OF_RANGE = '22003' NUMERIC_VALUE_OUT_OF_RANGE = '22003'
NULL_VALUE_NOT_ALLOWED_ = '22004' NULL_VALUE_NOT_ALLOWED = '22004'
ERROR_IN_ASSIGNMENT = '22005' ERROR_IN_ASSIGNMENT = '22005'
INVALID_DATETIME_FORMAT = '22007' INVALID_DATETIME_FORMAT = '22007'
DATETIME_FIELD_OVERFLOW = '22008' DATETIME_FIELD_OVERFLOW = '22008'
@ -175,7 +165,6 @@ INVALID_USE_OF_ESCAPE_CHARACTER = '2200C'
INVALID_ESCAPE_OCTET = '2200D' INVALID_ESCAPE_OCTET = '2200D'
ZERO_LENGTH_CHARACTER_STRING = '2200F' ZERO_LENGTH_CHARACTER_STRING = '2200F'
MOST_SPECIFIC_TYPE_MISMATCH = '2200G' MOST_SPECIFIC_TYPE_MISMATCH = '2200G'
SEQUENCE_GENERATOR_LIMIT_EXCEEDED = '2200H'
NOT_AN_XML_DOCUMENT = '2200L' NOT_AN_XML_DOCUMENT = '2200L'
INVALID_XML_DOCUMENT = '2200M' INVALID_XML_DOCUMENT = '2200M'
INVALID_XML_CONTENT = '2200N' INVALID_XML_CONTENT = '2200N'
@ -184,7 +173,6 @@ INVALID_XML_PROCESSING_INSTRUCTION = '2200T'
INVALID_INDICATOR_PARAMETER_VALUE = '22010' INVALID_INDICATOR_PARAMETER_VALUE = '22010'
SUBSTRING_ERROR = '22011' SUBSTRING_ERROR = '22011'
DIVISION_BY_ZERO = '22012' DIVISION_BY_ZERO = '22012'
INVALID_PRECEDING_OR_FOLLOWING_SIZE = '22013'
INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014' INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014'
INTERVAL_FIELD_OVERFLOW = '22015' INTERVAL_FIELD_OVERFLOW = '22015'
INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016' INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016'
@ -205,25 +193,6 @@ INVALID_ESCAPE_SEQUENCE = '22025'
STRING_DATA_LENGTH_MISMATCH = '22026' STRING_DATA_LENGTH_MISMATCH = '22026'
TRIM_ERROR = '22027' TRIM_ERROR = '22027'
ARRAY_SUBSCRIPT_ERROR = '2202E' ARRAY_SUBSCRIPT_ERROR = '2202E'
INVALID_TABLESAMPLE_REPEAT = '2202G'
INVALID_TABLESAMPLE_ARGUMENT = '2202H'
DUPLICATE_JSON_OBJECT_KEY_VALUE = '22030'
INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION = '22031'
INVALID_JSON_TEXT = '22032'
INVALID_SQL_JSON_SUBSCRIPT = '22033'
MORE_THAN_ONE_SQL_JSON_ITEM = '22034'
NO_SQL_JSON_ITEM = '22035'
NON_NUMERIC_SQL_JSON_ITEM = '22036'
NON_UNIQUE_KEYS_IN_A_JSON_OBJECT = '22037'
SINGLETON_SQL_JSON_ITEM_REQUIRED = '22038'
SQL_JSON_ARRAY_NOT_FOUND = '22039'
SQL_JSON_MEMBER_NOT_FOUND = '2203A'
SQL_JSON_NUMBER_NOT_FOUND = '2203B'
SQL_JSON_OBJECT_NOT_FOUND = '2203C'
TOO_MANY_JSON_ARRAY_ELEMENTS = '2203D'
TOO_MANY_JSON_OBJECT_MEMBERS = '2203E'
SQL_JSON_SCALAR_REQUIRED = '2203F'
SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE = '2203G'
FLOATING_POINT_EXCEPTION = '22P01' FLOATING_POINT_EXCEPTION = '22P01'
INVALID_TEXT_REPRESENTATION = '22P02' INVALID_TEXT_REPRESENTATION = '22P02'
INVALID_BINARY_REPRESENTATION = '22P03' INVALID_BINARY_REPRESENTATION = '22P03'
@ -255,8 +224,6 @@ SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED = '25007'
HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008' HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008'
NO_ACTIVE_SQL_TRANSACTION = '25P01' NO_ACTIVE_SQL_TRANSACTION = '25P01'
IN_FAILED_SQL_TRANSACTION = '25P02' IN_FAILED_SQL_TRANSACTION = '25P02'
IDLE_IN_TRANSACTION_SESSION_TIMEOUT = '25P03'
TRANSACTION_TIMEOUT = '25P04'
# Class 26 - Invalid SQL Statement Name # Class 26 - Invalid SQL Statement Name
INVALID_SQL_STATEMENT_NAME = '26000' INVALID_SQL_STATEMENT_NAME = '26000'
@ -277,9 +244,9 @@ INVALID_TRANSACTION_TERMINATION = '2D000'
# Class 2F - SQL Routine Exception # Class 2F - SQL Routine Exception
SQL_ROUTINE_EXCEPTION = '2F000' SQL_ROUTINE_EXCEPTION = '2F000'
MODIFYING_SQL_DATA_NOT_PERMITTED_ = '2F002' MODIFYING_SQL_DATA_NOT_PERMITTED = '2F002'
PROHIBITED_SQL_STATEMENT_ATTEMPTED_ = '2F003' PROHIBITED_SQL_STATEMENT_ATTEMPTED = '2F003'
READING_SQL_DATA_NOT_PERMITTED_ = '2F004' READING_SQL_DATA_NOT_PERMITTED = '2F004'
FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005' FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005'
# Class 34 - Invalid Cursor Name # Class 34 - Invalid Cursor Name
@ -298,7 +265,6 @@ INVALID_SQLSTATE_RETURNED = '39001'
NULL_VALUE_NOT_ALLOWED = '39004' NULL_VALUE_NOT_ALLOWED = '39004'
TRIGGER_PROTOCOL_VIOLATED = '39P01' TRIGGER_PROTOCOL_VIOLATED = '39P01'
SRF_PROTOCOL_VIOLATED = '39P02' SRF_PROTOCOL_VIOLATED = '39P02'
EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03'
# Class 3B - Savepoint Exception # Class 3B - Savepoint Exception
SAVEPOINT_EXCEPTION = '3B000' SAVEPOINT_EXCEPTION = '3B000'
@ -338,7 +304,6 @@ WRONG_OBJECT_TYPE = '42809'
INVALID_FOREIGN_KEY = '42830' INVALID_FOREIGN_KEY = '42830'
CANNOT_COERCE = '42846' CANNOT_COERCE = '42846'
UNDEFINED_FUNCTION = '42883' UNDEFINED_FUNCTION = '42883'
GENERATED_ALWAYS = '428C9'
RESERVED_NAME = '42939' RESERVED_NAME = '42939'
UNDEFINED_TABLE = '42P01' UNDEFINED_TABLE = '42P01'
UNDEFINED_PARAMETER = '42P02' UNDEFINED_PARAMETER = '42P02'
@ -384,7 +349,6 @@ OBJECT_NOT_IN_PREREQUISITE_STATE = '55000'
OBJECT_IN_USE = '55006' OBJECT_IN_USE = '55006'
CANT_CHANGE_RUNTIME_PARAM = '55P02' CANT_CHANGE_RUNTIME_PARAM = '55P02'
LOCK_NOT_AVAILABLE = '55P03' LOCK_NOT_AVAILABLE = '55P03'
UNSAFE_NEW_ENUM_VALUE_USAGE = '55P04'
# Class 57 - Operator Intervention # Class 57 - Operator Intervention
OPERATOR_INTERVENTION = '57000' OPERATOR_INTERVENTION = '57000'
@ -393,7 +357,6 @@ ADMIN_SHUTDOWN = '57P01'
CRASH_SHUTDOWN = '57P02' CRASH_SHUTDOWN = '57P02'
CANNOT_CONNECT_NOW = '57P03' CANNOT_CONNECT_NOW = '57P03'
DATABASE_DROPPED = '57P04' DATABASE_DROPPED = '57P04'
IDLE_SESSION_TIMEOUT = '57P05'
# Class 58 - System Error (errors external to PostgreSQL itself) # Class 58 - System Error (errors external to PostgreSQL itself)
SYSTEM_ERROR = '58000' SYSTEM_ERROR = '58000'
@ -401,9 +364,6 @@ IO_ERROR = '58030'
UNDEFINED_FILE = '58P01' UNDEFINED_FILE = '58P01'
DUPLICATE_FILE = '58P02' DUPLICATE_FILE = '58P02'
# Class 72 - Snapshot Failure
SNAPSHOT_TOO_OLD = '72000'
# Class F0 - Configuration File Error # Class F0 - Configuration File Error
CONFIG_FILE_ERROR = 'F0000' CONFIG_FILE_ERROR = 'F0000'
LOCK_FILE_EXISTS = 'F0001' LOCK_FILE_EXISTS = 'F0001'
@ -442,7 +402,6 @@ PLPGSQL_ERROR = 'P0000'
RAISE_EXCEPTION = 'P0001' RAISE_EXCEPTION = 'P0001'
NO_DATA_FOUND = 'P0002' NO_DATA_FOUND = 'P0002'
TOO_MANY_ROWS = 'P0003' TOO_MANY_ROWS = 'P0003'
ASSERT_FAILURE = 'P0004'
# Class XX - Internal Error # Class XX - Internal Error
INTERNAL_ERROR = 'XX000' INTERNAL_ERROR = 'XX000'

View File

@ -1,38 +0,0 @@
"""Error classes for PostgreSQL error codes
"""
# psycopg/errors.py - SQLSTATE and DB-API exceptions
#
# Copyright (C) 2018-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020-2021 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
#
# NOTE: the exceptions are injected into this module by the C extention.
#
def lookup(code):
"""Lookup an error code and return its exception class.
Raise `!KeyError` if the code is not found.
"""
from psycopg2._psycopg import sqlstate_errors # avoid circular import
return sqlstate_errors[code]

View File

@ -8,12 +8,11 @@ This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used - `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
by psycopg to adapt Python types to PostgreSQL ones by psycopg to adapt Python types to PostgreSQL ones
.. _PEP-246: https://www.python.org/dev/peps/pep-0246/ .. _PEP-246: http://www.python.org/peps/pep-0246.html
""" """
# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg # psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg
# #
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> # Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -33,64 +32,81 @@ This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details. # License for more details.
import re as _re from psycopg2._psycopg import UNICODE, INTEGER, LONGINTEGER, BOOLEAN, FLOAT
from psycopg2._psycopg import TIME, DATE, INTERVAL, DECIMAL
from psycopg2._psycopg import BINARYARRAY, BOOLEANARRAY, DATEARRAY, DATETIMEARRAY
from psycopg2._psycopg import DECIMALARRAY, FLOATARRAY, INTEGERARRAY, INTERVALARRAY
from psycopg2._psycopg import LONGINTEGERARRAY, ROWIDARRAY, STRINGARRAY, TIMEARRAY
from psycopg2._psycopg import UNICODEARRAY
from psycopg2._psycopg import ( # noqa from psycopg2._psycopg import Binary, Boolean, Int, Float, QuotedString, AsIs
BINARYARRAY, BOOLEAN, BOOLEANARRAY, BYTES, BYTESARRAY, DATE, DATEARRAY, try:
DATETIMEARRAY, DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER, from psycopg2._psycopg import MXDATE, MXDATETIME, MXINTERVAL, MXTIME
INTEGERARRAY, INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY, from psycopg2._psycopg import MXDATEARRAY, MXDATETIMEARRAY, MXINTERVALARRAY, MXTIMEARRAY
ROWIDARRAY, STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY, from psycopg2._psycopg import DateFromMx, TimeFromMx, TimestampFromMx
AsIs, Binary, Boolean, Float, Int, QuotedString, ) from psycopg2._psycopg import IntervalFromMx
except ImportError:
pass
from psycopg2._psycopg import ( # noqa try:
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY, from psycopg2._psycopg import PYDATE, PYDATETIME, PYINTERVAL, PYTIME
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY, from psycopg2._psycopg import PYDATEARRAY, PYDATETIMEARRAY, PYINTERVALARRAY, PYTIMEARRAY
DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, ) from psycopg2._psycopg import DateFromPy, TimeFromPy, TimestampFromPy
from psycopg2._psycopg import IntervalFromPy
except ImportError:
pass
from psycopg2._psycopg import ( # noqa from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid
adapt, adapters, encodings, connection, cursor, from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type
lobject, Xid, libpq_version, parse_dsn, quote_ident, from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics
string_types, binary_types, new_type, new_array_type, register_type,
ISQLQuote, Notify, Diagnostics, Column, ConnectionInfo,
QueryCanceledError, TransactionRollbackError,
set_wait_callback, get_wait_callback, encrypt_password, )
from psycopg2._psycopg import QueryCanceledError, TransactionRollbackError
try:
from psycopg2._psycopg import set_wait_callback, get_wait_callback
except ImportError:
pass
"""Isolation level values.""" """Isolation level values."""
ISOLATION_LEVEL_AUTOCOMMIT = 0 ISOLATION_LEVEL_AUTOCOMMIT = 0
ISOLATION_LEVEL_READ_UNCOMMITTED = 4 ISOLATION_LEVEL_READ_UNCOMMITTED = 4
ISOLATION_LEVEL_READ_COMMITTED = 1 ISOLATION_LEVEL_READ_COMMITTED = 1
ISOLATION_LEVEL_REPEATABLE_READ = 2 ISOLATION_LEVEL_REPEATABLE_READ = 2
ISOLATION_LEVEL_SERIALIZABLE = 3 ISOLATION_LEVEL_SERIALIZABLE = 3
ISOLATION_LEVEL_DEFAULT = None
"""psycopg connection status values.""" """psycopg connection status values."""
STATUS_SETUP = 0 STATUS_SETUP = 0
STATUS_READY = 1 STATUS_READY = 1
STATUS_BEGIN = 2 STATUS_BEGIN = 2
STATUS_SYNC = 3 # currently unused STATUS_SYNC = 3 # currently unused
STATUS_ASYNC = 4 # currently unused STATUS_ASYNC = 4 # currently unused
STATUS_PREPARED = 5 STATUS_PREPARED = 5
# This is a useful mnemonic to check if the connection is in a transaction # This is a usefull mnemonic to check if the connection is in a transaction
STATUS_IN_TRANSACTION = STATUS_BEGIN STATUS_IN_TRANSACTION = STATUS_BEGIN
"""psycopg asynchronous connection polling values""" """psycopg asynchronous connection polling values"""
POLL_OK = 0 POLL_OK = 0
POLL_READ = 1 POLL_READ = 1
POLL_WRITE = 2 POLL_WRITE = 2
POLL_ERROR = 3 POLL_ERROR = 3
"""Backend transaction status values.""" """Backend transaction status values."""
TRANSACTION_STATUS_IDLE = 0 TRANSACTION_STATUS_IDLE = 0
TRANSACTION_STATUS_ACTIVE = 1 TRANSACTION_STATUS_ACTIVE = 1
TRANSACTION_STATUS_INTRANS = 2 TRANSACTION_STATUS_INTRANS = 2
TRANSACTION_STATUS_INERROR = 3 TRANSACTION_STATUS_INERROR = 3
TRANSACTION_STATUS_UNKNOWN = 4 TRANSACTION_STATUS_UNKNOWN = 4
import sys as _sys
# Return bytes from a string
if _sys.version_info[0] < 3:
def b(s):
return s
else:
def b(s):
return s.encode('utf8')
def register_adapter(typ, callable): def register_adapter(typ, callable):
"""Register 'callable' as an ISQLQuote adapter for type 'typ'.""" """Register 'callable' as an ISQLQuote adapter for type 'typ'."""
@ -98,7 +114,7 @@ def register_adapter(typ, callable):
# The SQL_IN class is the official adapter for tuples starting from 2.0.6. # The SQL_IN class is the official adapter for tuples starting from 2.0.6.
class SQL_IN: class SQL_IN(object):
"""Adapt any iterable to an SQL quotable object.""" """Adapt any iterable to an SQL quotable object."""
def __init__(self, seq): def __init__(self, seq):
self._seq = seq self._seq = seq
@ -116,13 +132,13 @@ class SQL_IN:
if hasattr(obj, 'prepare'): if hasattr(obj, 'prepare'):
obj.prepare(self._conn) obj.prepare(self._conn)
qobjs = [o.getquoted() for o in pobjs] qobjs = [o.getquoted() for o in pobjs]
return b'(' + b', '.join(qobjs) + b')' return b('(') + b(', ').join(qobjs) + b(')')
def __str__(self): def __str__(self):
return str(self.getquoted()) return str(self.getquoted())
class NoneAdapter: class NoneAdapter(object):
"""Adapt None to NULL. """Adapt None to NULL.
This adapter is not used normally as a fast path in mogrify uses NULL, This adapter is not used normally as a fast path in mogrify uses NULL,
@ -131,82 +147,30 @@ class NoneAdapter:
def __init__(self, obj): def __init__(self, obj):
pass pass
def getquoted(self, _null=b"NULL"): def getquoted(self, _null=b("NULL")):
return _null return _null
def make_dsn(dsn=None, **kwargs):
"""Convert a set of keywords into a connection strings."""
if dsn is None and not kwargs:
return ''
# If no kwarg is specified don't mung the dsn, but verify it
if not kwargs:
parse_dsn(dsn)
return dsn
# Override the dsn with the parameters
if 'database' in kwargs:
if 'dbname' in kwargs:
raise TypeError(
"you can't specify both 'database' and 'dbname' arguments")
kwargs['dbname'] = kwargs.pop('database')
# Drop the None arguments
kwargs = {k: v for (k, v) in kwargs.items() if v is not None}
if dsn is not None:
tmp = parse_dsn(dsn)
tmp.update(kwargs)
kwargs = tmp
dsn = " ".join(["{}={}".format(k, _param_escape(str(v)))
for (k, v) in kwargs.items()])
# verify that the returned dsn is valid
parse_dsn(dsn)
return dsn
def _param_escape(s,
re_escape=_re.compile(r"([\\'])"),
re_space=_re.compile(r'\s')):
"""
Apply the escaping rule required by PQconnectdb
"""
if not s:
return "''"
s = re_escape.sub(r'\\\1', s)
if re_space.search(s):
s = "'" + s + "'"
return s
# Create default json typecasters for PostgreSQL 9.2 oids # Create default json typecasters for PostgreSQL 9.2 oids
from psycopg2._json import register_default_json, register_default_jsonb # noqa from psycopg2._json import register_default_json
try: try:
JSON, JSONARRAY = register_default_json() JSON, JSONARRAY = register_default_json()
JSONB, JSONBARRAY = register_default_jsonb()
except ImportError: except ImportError:
pass pass
del register_default_json, register_default_jsonb del register_default_json
# Create default Range typecasters # Create default Range typecasters
from psycopg2. _range import Range # noqa from psycopg2. _range import Range
del Range del Range
# Add the "cleaned" version of the encodings to the key. # Add the "cleaned" version of the encodings to the key.
# When the encoding is set its name is cleaned up from - and _ and turned # 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 # uppercase, so an encoding not respecting these rules wouldn't be found in the
# encodings keys and would raise an exception with the unicode typecaster # encodings keys and would raise an exception with the unicode typecaster
for k, v in list(encodings.items()): for k, v in encodings.items():
k = k.replace('_', '').replace('-', '').upper() k = k.replace('_', '').replace('-', '').upper()
encodings[k] = v encodings[k] = v

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,7 @@ This module implements thread-safe (and not) connection pools.
""" """
# psycopg/pool.py - pooling code for psycopg # psycopg/pool.py - pooling code for psycopg
# #
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> # Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -26,14 +25,14 @@ This module implements thread-safe (and not) connection pools.
# License for more details. # License for more details.
import psycopg2 import psycopg2
from psycopg2 import extensions as _ext import psycopg2.extensions as _ext
class PoolError(psycopg2.Error): class PoolError(psycopg2.Error):
pass pass
class AbstractConnectionPool: class AbstractConnectionPool(object):
"""Generic key-based pooling code.""" """Generic key-based pooling code."""
def __init__(self, minconn, maxconn, *args, **kwargs): def __init__(self, minconn, maxconn, *args, **kwargs):
@ -43,8 +42,8 @@ class AbstractConnectionPool:
with given parameters. The connection pool will support a maximum of with given parameters. The connection pool will support a maximum of
about 'maxconn' connections. about 'maxconn' connections.
""" """
self.minconn = int(minconn) self.minconn = minconn
self.maxconn = int(maxconn) self.maxconn = maxconn
self.closed = False self.closed = False
self._args = args self._args = args
@ -52,7 +51,7 @@ class AbstractConnectionPool:
self._pool = [] self._pool = []
self._used = {} self._used = {}
self._rused = {} # id(conn) -> key map self._rused = {} # id(conn) -> key map
self._keys = 0 self._keys = 0
for i in range(self.minconn): for i in range(self.minconn):
@ -75,10 +74,8 @@ class AbstractConnectionPool:
def _getconn(self, key=None): def _getconn(self, key=None):
"""Get a free connection and assign it to 'key' if not None.""" """Get a free connection and assign it to 'key' if not None."""
if self.closed: if self.closed: raise PoolError("connection pool is closed")
raise PoolError("connection pool is closed") if key is None: key = self._getkey()
if key is None:
key = self._getkey()
if key in self._used: if key in self._used:
return self._used[key] return self._used[key]
@ -89,24 +86,22 @@ class AbstractConnectionPool:
return conn return conn
else: else:
if len(self._used) == self.maxconn: if len(self._used) == self.maxconn:
raise PoolError("connection pool exhausted") raise PoolError("connection pool exausted")
return self._connect(key) return self._connect(key)
def _putconn(self, conn, key=None, close=False): def _putconn(self, conn, key=None, close=False):
"""Put away a connection.""" """Put away a connection."""
if self.closed: if self.closed: raise PoolError("connection pool is closed")
raise PoolError("connection pool is closed") if key is None: key = self._rused.get(id(conn))
if key is None: if not key:
key = self._rused.get(id(conn)) raise PoolError("trying to put unkeyed connection")
if key is None:
raise PoolError("trying to put unkeyed connection")
if len(self._pool) < self.minconn and not close: if len(self._pool) < self.minconn and not close:
# Return the connection into a consistent state before putting # Return the connection into a consistent state before putting
# it back into the pool # it back into the pool
if not conn.closed: if not conn.closed:
status = conn.info.transaction_status status = conn.get_transaction_status()
if status == _ext.TRANSACTION_STATUS_UNKNOWN: if status == _ext.TRANSACTION_STATUS_UNKNOWN:
# server connection lost # server connection lost
conn.close() conn.close()
@ -134,12 +129,11 @@ class AbstractConnectionPool:
an already closed connection. If you call .closeall() make sure an already closed connection. If you call .closeall() make sure
your code can deal with it. your code can deal with it.
""" """
if self.closed: if self.closed: raise PoolError("connection pool is closed")
raise PoolError("connection pool is closed")
for conn in self._pool + list(self._used.values()): for conn in self._pool + list(self._used.values()):
try: try:
conn.close() conn.close()
except Exception: except:
pass pass
self.closed = True self.closed = True
@ -149,7 +143,7 @@ class SimpleConnectionPool(AbstractConnectionPool):
getconn = AbstractConnectionPool._getconn getconn = AbstractConnectionPool._getconn
putconn = AbstractConnectionPool._putconn putconn = AbstractConnectionPool._putconn
closeall = AbstractConnectionPool._closeall closeall = AbstractConnectionPool._closeall
class ThreadedConnectionPool(AbstractConnectionPool): class ThreadedConnectionPool(AbstractConnectionPool):
@ -185,3 +179,57 @@ class ThreadedConnectionPool(AbstractConnectionPool):
self._closeall() self._closeall()
finally: finally:
self._lock.release() self._lock.release()
class PersistentConnectionPool(AbstractConnectionPool):
"""A pool that assigns persistent connections to different threads.
Note that this connection pool generates by itself the required keys
using the current thread id. This means that until a thread puts away
a connection it will always get the same connection object by successive
`!getconn()` calls. This also means that a thread can't use more than one
single connection from the pool.
"""
def __init__(self, minconn, maxconn, *args, **kwargs):
"""Initialize the threading lock."""
import warnings
warnings.warn("deprecated: use ZPsycopgDA.pool implementation",
DeprecationWarning)
import threading
AbstractConnectionPool.__init__(
self, minconn, maxconn, *args, **kwargs)
self._lock = threading.Lock()
# 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
def getconn(self):
"""Generate thread id and return a connection."""
key = self.__thread.get_ident()
self._lock.acquire()
try:
return self._getconn(key)
finally:
self._lock.release()
def putconn(self, conn=None, close=False):
"""Put away an unused connection."""
key = self.__thread.get_ident()
self._lock.acquire()
try:
if not conn: conn = self._used[key]
self._putconn(conn, key, close)
finally:
self._lock.release()
def closeall(self):
"""Close all connections (even the one currently in use.)"""
self._lock.acquire()
try:
self._closeall()
finally:
self._lock.release()

95
lib/psycopg1.py Normal file
View File

@ -0,0 +1,95 @@
"""psycopg 1.1.x compatibility module
This module uses the new style connection and cursor types to build a psycopg
1.1.1.x compatibility layer. It should be considered a temporary hack to run
old code while porting to psycopg 2. Import it as follows::
from psycopg2 import psycopg1 as psycopg
"""
# psycopg/psycopg1.py - psycopg 1.1.x compatibility module
#
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import _psycopg as _2psycopg
from psycopg2.extensions import cursor as _2cursor
from psycopg2.extensions import connection as _2connection
from psycopg2 import *
import psycopg2.extensions as _ext
_2connect = connect
def connect(*args, **kwargs):
"""connect(dsn, ...) -> new psycopg 1.1.x compatible connection object"""
kwargs['connection_factory'] = connection
conn = _2connect(*args, **kwargs)
conn.set_isolation_level(_ext.ISOLATION_LEVEL_READ_COMMITTED)
return conn
class connection(_2connection):
"""psycopg 1.1.x connection."""
def cursor(self):
"""cursor() -> new psycopg 1.1.x compatible cursor object"""
return _2connection.cursor(self, cursor_factory=cursor)
def autocommit(self, on_off=1):
"""autocommit(on_off=1) -> switch autocommit on (1) or off (0)"""
if on_off > 0:
self.set_isolation_level(_ext.ISOLATION_LEVEL_AUTOCOMMIT)
else:
self.set_isolation_level(_ext.ISOLATION_LEVEL_READ_COMMITTED)
class cursor(_2cursor):
"""psycopg 1.1.x cursor.
Note that this cursor implements the exact procedure used by psycopg 1 to
build dictionaries out of result rows. The DictCursor in the
psycopg.extras modules implements a much better and faster algorithm.
"""
def __build_dict(self, row):
res = {}
for i in range(len(self.description)):
res[self.description[i][0]] = row[i]
return res
def dictfetchone(self):
row = _2cursor.fetchone(self)
if row:
return self.__build_dict(row)
else:
return row
def dictfetchmany(self, size):
res = []
rows = _2cursor.fetchmany(self, size)
for row in rows:
res.append(self.__build_dict(row))
return res
def dictfetchall(self):
res = []
rows = _2cursor.fetchall(self)
for row in rows:
res.append(self.__build_dict(row))
return res

View File

@ -1,455 +0,0 @@
"""SQL composition utility module
"""
# psycopg/sql.py - SQL composition utility module
#
# Copyright (C) 2016-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
# Copyright (C) 2020-2021 The Psycopg Team
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import string
from psycopg2 import extensions as ext
_formatter = string.Formatter()
class Composable:
"""
Abstract base class for objects that can be used to compose an SQL string.
`!Composable` objects can be passed directly to `~cursor.execute()`,
`~cursor.executemany()`, `~cursor.copy_expert()` in place of the query
string.
`!Composable` objects can be joined using the ``+`` operator: the result
will be a `Composed` instance containing the objects joined. The operator
``*`` is also supported with an integer argument: the result is a
`!Composed` instance containing the left argument repeated as many times as
requested.
"""
def __init__(self, wrapped):
self._wrapped = wrapped
def __repr__(self):
return f"{self.__class__.__name__}({self._wrapped!r})"
def as_string(self, context):
"""
Return the string value of the object.
:param context: the context to evaluate the string into.
:type context: `connection` or `cursor`
The method is automatically invoked by `~cursor.execute()`,
`~cursor.executemany()`, `~cursor.copy_expert()` if a `!Composable` is
passed instead of the query string.
"""
raise NotImplementedError
def __add__(self, other):
if isinstance(other, Composed):
return Composed([self]) + other
if isinstance(other, Composable):
return Composed([self]) + Composed([other])
else:
return NotImplemented
def __mul__(self, n):
return Composed([self] * n)
def __eq__(self, other):
return type(self) is type(other) and self._wrapped == other._wrapped
def __ne__(self, other):
return not self.__eq__(other)
class Composed(Composable):
"""
A `Composable` object made of a sequence of `!Composable`.
The object is usually created using `!Composable` operators and methods.
However it is possible to create a `!Composed` directly specifying a
sequence of `!Composable` as arguments.
Example::
>>> comp = sql.Composed(
... [sql.SQL("insert into "), sql.Identifier("table")])
>>> print(comp.as_string(conn))
insert into "table"
`!Composed` objects are iterable (so they can be used in `SQL.join` for
instance).
"""
def __init__(self, seq):
wrapped = []
for i in seq:
if not isinstance(i, Composable):
raise TypeError(
f"Composed elements must be Composable, got {i!r} instead")
wrapped.append(i)
super().__init__(wrapped)
@property
def seq(self):
"""The list of the content of the `!Composed`."""
return list(self._wrapped)
def as_string(self, context):
rv = []
for i in self._wrapped:
rv.append(i.as_string(context))
return ''.join(rv)
def __iter__(self):
return iter(self._wrapped)
def __add__(self, other):
if isinstance(other, Composed):
return Composed(self._wrapped + other._wrapped)
if isinstance(other, Composable):
return Composed(self._wrapped + [other])
else:
return NotImplemented
def join(self, joiner):
"""
Return a new `!Composed` interposing the *joiner* with the `!Composed` items.
The *joiner* must be a `SQL` or a string which will be interpreted as
an `SQL`.
Example::
>>> fields = sql.Identifier('foo') + sql.Identifier('bar') # a Composed
>>> print(fields.join(', ').as_string(conn))
"foo", "bar"
"""
if isinstance(joiner, str):
joiner = SQL(joiner)
elif not isinstance(joiner, SQL):
raise TypeError(
"Composed.join() argument must be a string or an SQL")
return joiner.join(self)
class SQL(Composable):
"""
A `Composable` representing a snippet of SQL statement.
`!SQL` exposes `join()` and `format()` methods useful to create a template
where to merge variable parts of a query (for instance field or table
names).
The *string* doesn't undergo any form of escaping, so it is not suitable to
represent variable identifiers or values: you should only use it to pass
constant strings representing templates or snippets of SQL statements; use
other objects such as `Identifier` or `Literal` to represent variable
parts.
Example::
>>> query = sql.SQL("select {0} from {1}").format(
... sql.SQL(', ').join([sql.Identifier('foo'), sql.Identifier('bar')]),
... sql.Identifier('table'))
>>> print(query.as_string(conn))
select "foo", "bar" from "table"
"""
def __init__(self, string):
if not isinstance(string, str):
raise TypeError("SQL values must be strings")
super().__init__(string)
@property
def string(self):
"""The string wrapped by the `!SQL` object."""
return self._wrapped
def as_string(self, context):
return self._wrapped
def format(self, *args, **kwargs):
"""
Merge `Composable` objects into a template.
:param `Composable` args: parameters to replace to numbered
(``{0}``, ``{1}``) or auto-numbered (``{}``) placeholders
:param `Composable` kwargs: parameters to replace to named (``{name}``)
placeholders
:return: the union of the `!SQL` string with placeholders replaced
:rtype: `Composed`
The method is similar to the Python `str.format()` method: the string
template supports auto-numbered (``{}``), numbered (``{0}``,
``{1}``...), and named placeholders (``{name}``), with positional
arguments replacing the numbered placeholders and keywords replacing
the named ones. However placeholder modifiers (``{0!r}``, ``{0:<10}``)
are not supported. Only `!Composable` objects can be passed to the
template.
Example::
>>> print(sql.SQL("select * from {} where {} = %s")
... .format(sql.Identifier('people'), sql.Identifier('id'))
... .as_string(conn))
select * from "people" where "id" = %s
>>> print(sql.SQL("select * from {tbl} where {pkey} = %s")
... .format(tbl=sql.Identifier('people'), pkey=sql.Identifier('id'))
... .as_string(conn))
select * from "people" where "id" = %s
"""
rv = []
autonum = 0
for pre, name, spec, conv in _formatter.parse(self._wrapped):
if spec:
raise ValueError("no format specification supported by SQL")
if conv:
raise ValueError("no format conversion supported by SQL")
if pre:
rv.append(SQL(pre))
if name is None:
continue
if name.isdigit():
if autonum:
raise ValueError(
"cannot switch from automatic field numbering to manual")
rv.append(args[int(name)])
autonum = None
elif not name:
if autonum is None:
raise ValueError(
"cannot switch from manual field numbering to automatic")
rv.append(args[autonum])
autonum += 1
else:
rv.append(kwargs[name])
return Composed(rv)
def join(self, seq):
"""
Join a sequence of `Composable`.
:param seq: the elements to join.
:type seq: iterable of `!Composable`
Use the `!SQL` object's *string* to separate the elements in *seq*.
Note that `Composed` objects are iterable too, so they can be used as
argument for this method.
Example::
>>> snip = sql.SQL(', ').join(
... sql.Identifier(n) for n in ['foo', 'bar', 'baz'])
>>> print(snip.as_string(conn))
"foo", "bar", "baz"
"""
rv = []
it = iter(seq)
try:
rv.append(next(it))
except StopIteration:
pass
else:
for i in it:
rv.append(self)
rv.append(i)
return Composed(rv)
class Identifier(Composable):
"""
A `Composable` representing an SQL identifier or a dot-separated sequence.
Identifiers usually represent names of database objects, such as tables or
fields. PostgreSQL identifiers follow `different rules`__ than SQL string
literals for escaping (e.g. they use double quotes instead of single).
.. __: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html# \
SQL-SYNTAX-IDENTIFIERS
Example::
>>> t1 = sql.Identifier("foo")
>>> t2 = sql.Identifier("ba'r")
>>> t3 = sql.Identifier('ba"z')
>>> print(sql.SQL(', ').join([t1, t2, t3]).as_string(conn))
"foo", "ba'r", "ba""z"
Multiple strings can be passed to the object to represent a qualified name,
i.e. a dot-separated sequence of identifiers.
Example::
>>> query = sql.SQL("select {} from {}").format(
... sql.Identifier("table", "field"),
... sql.Identifier("schema", "table"))
>>> print(query.as_string(conn))
select "table"."field" from "schema"."table"
"""
def __init__(self, *strings):
if not strings:
raise TypeError("Identifier cannot be empty")
for s in strings:
if not isinstance(s, str):
raise TypeError("SQL identifier parts must be strings")
super().__init__(strings)
@property
def strings(self):
"""A tuple with the strings wrapped by the `Identifier`."""
return self._wrapped
@property
def string(self):
"""The string wrapped by the `Identifier`.
"""
if len(self._wrapped) == 1:
return self._wrapped[0]
else:
raise AttributeError(
"the Identifier wraps more than one than one string")
def __repr__(self):
return f"{self.__class__.__name__}({', '.join(map(repr, self._wrapped))})"
def as_string(self, context):
return '.'.join(ext.quote_ident(s, context) for s in self._wrapped)
class Literal(Composable):
"""
A `Composable` representing an SQL value to include in a query.
Usually you will want to include placeholders in the query and pass values
as `~cursor.execute()` arguments. If however you really really need to
include a literal value in the query you can use this object.
The string returned by `!as_string()` follows the normal :ref:`adaptation
rules <python-types-adaptation>` for Python objects.
Example::
>>> s1 = sql.Literal("foo")
>>> s2 = sql.Literal("ba'r")
>>> s3 = sql.Literal(42)
>>> print(sql.SQL(', ').join([s1, s2, s3]).as_string(conn))
'foo', 'ba''r', 42
"""
@property
def wrapped(self):
"""The object wrapped by the `!Literal`."""
return self._wrapped
def as_string(self, context):
# is it a connection or cursor?
if isinstance(context, ext.connection):
conn = context
elif isinstance(context, ext.cursor):
conn = context.connection
else:
raise TypeError("context must be a connection or a cursor")
a = ext.adapt(self._wrapped)
if hasattr(a, 'prepare'):
a.prepare(conn)
rv = a.getquoted()
if isinstance(rv, bytes):
rv = rv.decode(ext.encodings[conn.encoding])
return rv
class Placeholder(Composable):
"""A `Composable` representing a placeholder for query parameters.
If the name is specified, generate a named placeholder (e.g. ``%(name)s``),
otherwise generate a positional placeholder (e.g. ``%s``).
The object is useful to generate SQL queries with a variable number of
arguments.
Examples::
>>> names = ['foo', 'bar', 'baz']
>>> q1 = sql.SQL("insert into table ({}) values ({})").format(
... sql.SQL(', ').join(map(sql.Identifier, names)),
... sql.SQL(', ').join(sql.Placeholder() * len(names)))
>>> print(q1.as_string(conn))
insert into table ("foo", "bar", "baz") values (%s, %s, %s)
>>> q2 = sql.SQL("insert into table ({}) values ({})").format(
... sql.SQL(', ').join(map(sql.Identifier, names)),
... sql.SQL(', ').join(map(sql.Placeholder, names)))
>>> print(q2.as_string(conn))
insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s)
"""
def __init__(self, name=None):
if isinstance(name, str):
if ')' in name:
raise ValueError(f"invalid name: {name!r}")
elif name is not None:
raise TypeError(f"expected string or None as name, got {name!r}")
super().__init__(name)
@property
def name(self):
"""The name of the `!Placeholder`."""
return self._wrapped
def __repr__(self):
if self._wrapped is None:
return f"{self.__class__.__name__}()"
else:
return f"{self.__class__.__name__}({self._wrapped!r})"
def as_string(self, context):
if self._wrapped is not None:
return f"%({self._wrapped})s"
else:
return "%s"
# Literals
NULL = SQL("NULL")
DEFAULT = SQL("DEFAULT")

View File

@ -6,8 +6,7 @@ functions or used to set the .tzinfo_factory attribute in cursors.
""" """
# psycopg/tz.py - tzinfo implementation # psycopg/tz.py - tzinfo implementation
# #
# Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> # Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
# Copyright (C) 2020-2021 The Psycopg Team
# #
# psycopg2 is free software: you can redistribute it and/or modify it # psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published # under the terms of the GNU Lesser General Public License as published
@ -32,7 +31,6 @@ import time
ZERO = datetime.timedelta(0) ZERO = datetime.timedelta(0)
class FixedOffsetTimezone(datetime.tzinfo): class FixedOffsetTimezone(datetime.tzinfo):
"""Fixed offset in minutes east from UTC. """Fixed offset in minutes east from UTC.
@ -45,12 +43,7 @@ class FixedOffsetTimezone(datetime.tzinfo):
offset and name that instance will be returned. This saves memory and offset and name that instance will be returned. This saves memory and
improves comparability. improves comparability.
.. versionchanged:: 2.9 .. __: http://docs.python.org/library/datetime.html#datetime-tzinfo
The constructor can take either a timedelta or a number of minutes of
offset. Previously only minutes were supported.
.. __: https://docs.python.org/library/datetime.html
""" """
_name = None _name = None
_offset = ZERO _offset = ZERO
@ -59,9 +52,7 @@ class FixedOffsetTimezone(datetime.tzinfo):
def __init__(self, offset=None, name=None): def __init__(self, offset=None, name=None):
if offset is not None: if offset is not None:
if not isinstance(offset, datetime.timedelta): self._offset = datetime.timedelta(minutes = offset)
offset = datetime.timedelta(minutes=offset)
self._offset = offset
if name is not None: if name is not None:
self._name = name self._name = name
@ -72,28 +63,18 @@ class FixedOffsetTimezone(datetime.tzinfo):
try: try:
return cls._cache[key] return cls._cache[key]
except KeyError: except KeyError:
tz = super().__new__(cls, offset, name) tz = super(FixedOffsetTimezone, cls).__new__(cls, offset, name)
cls._cache[key] = tz cls._cache[key] = tz
return tz return tz
def __repr__(self): def __repr__(self):
offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \ return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \
% (self._offset, self._name) % (offset_mins, self._name)
def __eq__(self, other):
if isinstance(other, FixedOffsetTimezone):
return self._offset == other._offset
else:
return NotImplemented
def __ne__(self, other):
if isinstance(other, FixedOffsetTimezone):
return self._offset != other._offset
else:
return NotImplemented
def __getinitargs__(self): def __getinitargs__(self):
return self._offset, self._name offset_mins = self._offset.seconds // 60 + self._offset.days * 24 * 60
return (offset_mins, self._name)
def utcoffset(self, dt): def utcoffset(self, dt):
return self._offset return self._offset
@ -101,29 +82,26 @@ class FixedOffsetTimezone(datetime.tzinfo):
def tzname(self, dt): def tzname(self, dt):
if self._name is not None: if self._name is not None:
return self._name return self._name
else:
minutes, seconds = divmod(self._offset.total_seconds(), 60) seconds = self._offset.seconds + self._offset.days * 86400
hours, minutes = divmod(minutes, 60) hours, seconds = divmod(seconds, 3600)
rv = "%+03d" % hours minutes = seconds/60
if minutes or seconds: if minutes:
rv += ":%02d" % minutes return "%+03d:%d" % (hours, minutes)
if seconds: else:
rv += ":%02d" % seconds return "%+03d" % hours
return rv
def dst(self, dt): def dst(self, dt):
return ZERO return ZERO
STDOFFSET = datetime.timedelta(seconds=-time.timezone) STDOFFSET = datetime.timedelta(seconds = -time.timezone)
if time.daylight: if time.daylight:
DSTOFFSET = datetime.timedelta(seconds=-time.altzone) DSTOFFSET = datetime.timedelta(seconds = -time.altzone)
else: else:
DSTOFFSET = STDOFFSET DSTOFFSET = STDOFFSET
DSTDIFF = DSTOFFSET - STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET
class LocalTimezone(datetime.tzinfo): class LocalTimezone(datetime.tzinfo):
"""Platform idea of local timezone. """Platform idea of local timezone.
@ -152,7 +130,6 @@ class LocalTimezone(datetime.tzinfo):
tt = time.localtime(stamp) tt = time.localtime(stamp)
return tt.tm_isdst > 0 return tt.tm_isdst > 0
LOCAL = LocalTimezone() LOCAL = LocalTimezone()
# TODO: pre-generate some interesting time zones? # TODO: pre-generate some interesting time zones?

View File

@ -1,7 +1,6 @@
/* adapter_asis.c - adapt types as they are /* adapter_asis.c - adapt types as they are
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -45,12 +44,14 @@ asis_getquoted(asisObject *self, PyObject *args)
} }
else { else {
rv = PyObject_Str(self->wrapped); rv = PyObject_Str(self->wrapped);
/* unicode to bytes */ #if PY_MAJOR_VERSION > 2
/* unicode to bytes in Py3 */
if (rv) { if (rv) {
PyObject *tmp = PyUnicode_AsUTF8String(rv); PyObject *tmp = PyUnicode_AsUTF8String(rv);
Py_DECREF(rv); Py_DECREF(rv);
rv = tmp; rv = tmp;
} }
#endif
} }
return rv; return rv;
@ -59,7 +60,7 @@ asis_getquoted(asisObject *self, PyObject *args)
static PyObject * static PyObject *
asis_str(asisObject *self) asis_str(asisObject *self)
{ {
return psyco_ensure_text(asis_getquoted(self, NULL)); return psycopg_ensure_text(asis_getquoted(self, NULL));
} }
static PyObject * static PyObject *
@ -148,6 +149,12 @@ asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0); return type->tp_alloc(type, 0);
} }
static PyObject *
asis_repr(asisObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.AsIs object at %p>", self);
}
/* object type */ /* object type */
@ -156,14 +163,14 @@ asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PyTypeObject asisType = { PyTypeObject asisType = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.AsIs", "psycopg2._psycopg.AsIs",
sizeof(asisObject), 0, sizeof(asisObject), 0,
asis_dealloc, /*tp_dealloc*/ asis_dealloc, /*tp_dealloc*/
0, /*tp_print*/ 0, /*tp_print*/
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
0, /*tp_compare*/ 0, /*tp_compare*/
0, /*tp_repr*/ (reprfunc)asis_repr, /*tp_repr*/
0, /*tp_as_number*/ 0, /*tp_as_number*/
0, /*tp_as_sequence*/ 0, /*tp_as_sequence*/
0, /*tp_as_mapping*/ 0, /*tp_as_mapping*/
@ -193,3 +200,17 @@ PyTypeObject asisType = {
0, /*tp_alloc*/ 0, /*tp_alloc*/
asis_new, /*tp_new*/ 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

@ -1,7 +1,6 @@
/* adapter_asis.h - definition for the psycopg AsIs type wrapper /* adapter_asis.h - definition for the psycopg AsIs type wrapper
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -41,6 +40,12 @@ typedef struct {
} asisObject; } 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -1,7 +1,6 @@
/* adapter_binary.c - Binary objects /* adapter_binary.c - Binary objects
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -40,12 +39,17 @@ static unsigned char *
binary_escape(unsigned char *from, size_t from_length, binary_escape(unsigned char *from, size_t from_length,
size_t *to_length, PGconn *conn) size_t *to_length, PGconn *conn)
{ {
#if PG_VERSION_HEX >= 0x080104
if (conn) if (conn)
return PQescapeByteaConn(conn, from, from_length, to_length); return PQescapeByteaConn(conn, from, from_length, to_length);
else else
#endif
return PQescapeBytea(from, from_length, to_length); return PQescapeBytea(from, from_length, to_length);
} }
#define HAS_BUFFER (PY_MAJOR_VERSION < 3)
#define HAS_MEMORYVIEW (PY_MAJOR_VERSION > 2 || PY_MINOR_VERSION >= 6)
/* binary_quote - do the quote process on plain and unicode strings */ /* binary_quote - do the quote process on plain and unicode strings */
static PyObject * static PyObject *
@ -56,8 +60,10 @@ binary_quote(binaryObject *self)
Py_ssize_t buffer_len; Py_ssize_t buffer_len;
size_t len = 0; size_t len = 0;
PyObject *rv = NULL; PyObject *rv = NULL;
#if HAS_MEMORYVIEW
Py_buffer view; Py_buffer view;
int got_view = 0; int got_view = 0;
#endif
/* Allow Binary(None) to work */ /* Allow Binary(None) to work */
if (self->wrapped == Py_None) { if (self->wrapped == Py_None) {
@ -67,6 +73,8 @@ binary_quote(binaryObject *self)
} }
/* if we got a plain string or a buffer we escape it and save the buffer */ /* if we got a plain string or a buffer we escape it and save the buffer */
#if HAS_MEMORYVIEW
if (PyObject_CheckBuffer(self->wrapped)) { if (PyObject_CheckBuffer(self->wrapped)) {
if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) { if (0 > PyObject_GetBuffer(self->wrapped, &view, PyBUF_CONTIG_RO)) {
goto exit; goto exit;
@ -75,6 +83,16 @@ binary_quote(binaryObject *self)
buffer = (const char *)(view.buf); buffer = (const char *)(view.buf);
buffer_len = view.len; buffer_len = view.len;
} }
#endif
#if HAS_BUFFER
if (!buffer && (Bytes_Check(self->wrapped) || PyBuffer_Check(self->wrapped))) {
if (PyObject_AsReadBuffer(self->wrapped, (const void **)&buffer,
&buffer_len) < 0) {
goto exit;
}
}
#endif
if (!buffer) { if (!buffer) {
goto exit; goto exit;
@ -98,7 +116,9 @@ binary_quote(binaryObject *self)
exit: exit:
if (to) { PQfreemem(to); } if (to) { PQfreemem(to); }
#if HAS_MEMORYVIEW
if (got_view) { PyBuffer_Release(&view); } if (got_view) { PyBuffer_Release(&view); }
#endif
/* if the wrapped object is not bytes or a buffer, this is an error */ /* if the wrapped object is not bytes or a buffer, this is an error */
if (!rv && !PyErr_Occurred()) { if (!rv && !PyErr_Occurred()) {
@ -124,7 +144,7 @@ binary_getquoted(binaryObject *self, PyObject *args)
static PyObject * static PyObject *
binary_str(binaryObject *self) binary_str(binaryObject *self)
{ {
return psyco_ensure_text(binary_getquoted(self, NULL)); return psycopg_ensure_text(binary_getquoted(self, NULL));
} }
static PyObject * static PyObject *
@ -234,6 +254,11 @@ binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0); return type->tp_alloc(type, 0);
} }
static PyObject *
binary_repr(binaryObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Binary object at %p>", self);
}
/* object type */ /* object type */
@ -242,14 +267,14 @@ binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PyTypeObject binaryType = { PyTypeObject binaryType = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Binary", "psycopg2._psycopg.Binary",
sizeof(binaryObject), 0, sizeof(binaryObject), 0,
binary_dealloc, /*tp_dealloc*/ binary_dealloc, /*tp_dealloc*/
0, /*tp_print*/ 0, /*tp_print*/
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
0, /*tp_compare*/ 0, /*tp_compare*/
0, /*tp_repr*/ (reprfunc)binary_repr, /*tp_repr*/
0, /*tp_as_number*/ 0, /*tp_as_number*/
0, /*tp_as_sequence*/ 0, /*tp_as_sequence*/
0, /*tp_as_mapping*/ 0, /*tp_as_mapping*/
@ -279,3 +304,17 @@ PyTypeObject binaryType = {
0, /*tp_alloc*/ 0, /*tp_alloc*/
binary_new, /*tp_new*/ 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

@ -1,7 +1,6 @@
/* adapter_binary.h - definition for the Binary type /* adapter_binary.h - definition for the Binary type
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -41,6 +40,13 @@ typedef struct {
PyObject *conn; PyObject *conn;
} binaryObject; } 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 bynary string value."
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -1,7 +1,6 @@
/* adapter_datetime.c - python date/time objects /* adapter_datetime.c - python date/time objects
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -36,9 +35,14 @@
#include <string.h> #include <string.h>
RAISES_NEG int extern HIDDEN PyObject *pyPsycopgTzModule;
adapter_datetime_init(void) extern HIDDEN PyObject *pyPsycopgTzLOCAL;
int
psyco_adapter_datetime_init(void)
{ {
Dprintf("psyco_adapter_datetime_init: datetime init");
PyDateTime_IMPORT; PyDateTime_IMPORT;
if (!PyDateTimeAPI) { if (!PyDateTimeAPI) {
@ -61,10 +65,7 @@ _pydatetime_string_date_time(pydatetimeObject *self)
char *fmt = NULL; char *fmt = NULL;
switch (self->type) { switch (self->type) {
case PSYCO_DATETIME_TIME: case PSYCO_DATETIME_TIME:
tz = PyObject_GetAttrString(self->wrapped, "tzinfo"); fmt = "'%s'::time";
if (!tz) { goto error; }
fmt = (tz == Py_None) ? "'%s'::time" : "'%s'::timetz";
Py_DECREF(tz);
break; break;
case PSYCO_DATETIME_DATE: case PSYCO_DATETIME_DATE:
fmt = "'%s'::date"; fmt = "'%s'::date";
@ -77,7 +78,7 @@ _pydatetime_string_date_time(pydatetimeObject *self)
break; break;
} }
if (!(iso = psyco_ensure_bytes( if (!(iso = psycopg_ensure_bytes(
PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) { PyObject_CallMethod(self->wrapped, "isoformat", NULL)))) {
goto error; goto error;
} }
@ -99,7 +100,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
char buffer[8]; char buffer[8];
int i; int i;
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj); int a = obj->microseconds;
for (i=0; i < 6 ; i++) { for (i=0; i < 6 ; i++) {
buffer[5-i] = '0' + (a % 10); buffer[5-i] = '0' + (a % 10);
@ -108,9 +109,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
buffer[6] = '\0'; buffer[6] = '\0';
return Bytes_FromFormat("'%d days %d.%s seconds'::interval", return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
PyDateTime_DELTA_GET_DAYS(obj), obj->days, obj->seconds, buffer);
PyDateTime_DELTA_GET_SECONDS(obj),
buffer);
} }
static PyObject * static PyObject *
@ -127,7 +126,7 @@ pydatetime_getquoted(pydatetimeObject *self, PyObject *args)
static PyObject * static PyObject *
pydatetime_str(pydatetimeObject *self) pydatetime_str(pydatetimeObject *self)
{ {
return psyco_ensure_text(pydatetime_getquoted(self, NULL)); return psycopg_ensure_text(pydatetime_getquoted(self, NULL));
} }
static PyObject * static PyObject *
@ -215,6 +214,12 @@ pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0); return type->tp_alloc(type, 0);
} }
static PyObject *
pydatetime_repr(pydatetimeObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.datetime object at %p>",
self);
}
/* object type */ /* object type */
@ -230,7 +235,7 @@ PyTypeObject pydatetimeType = {
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
0, /*tp_compare*/ 0, /*tp_compare*/
0, /*tp_repr*/ (reprfunc)pydatetime_repr, /*tp_repr*/
0, /*tp_as_number*/ 0, /*tp_as_number*/
0, /*tp_as_sequence*/ 0, /*tp_as_sequence*/
0, /*tp_as_mapping*/ 0, /*tp_as_mapping*/
@ -264,6 +269,8 @@ PyTypeObject pydatetimeType = {
/** module-level functions **/ /** module-level functions **/
#ifdef PSYCOPG_DEFAULT_PYDATETIME
PyObject * PyObject *
psyco_Date(PyObject *self, PyObject *args) psyco_Date(PyObject *self, PyObject *args)
{ {
@ -385,9 +392,9 @@ psyco_DateFromTicks(PyObject *self, PyObject *args)
Py_DECREF(args); Py_DECREF(args);
} }
} }
else { else {
PyErr_SetString(InterfaceError, "failed localtime call"); PyErr_SetString(InterfaceError, "failed localtime call");
} }
return res; return res;
} }
@ -413,9 +420,9 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args)
Py_DECREF(args); Py_DECREF(args);
} }
} }
else { else {
PyErr_SetString(InterfaceError, "failed localtime call"); PyErr_SetString(InterfaceError, "failed localtime call");
} }
return res; return res;
} }
@ -423,8 +430,6 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args)
PyObject * PyObject *
psyco_TimestampFromTicks(PyObject *self, PyObject *args) psyco_TimestampFromTicks(PyObject *self, PyObject *args)
{ {
pydatetimeObject *wrapper = NULL;
PyObject *dt_aware = NULL;
PyObject *res = NULL; PyObject *res = NULL;
struct tm tm; struct tm tm;
time_t t; time_t t;
@ -435,37 +440,21 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
t = (time_t)floor(ticks); t = (time_t)floor(ticks);
ticks -= (double)t; ticks -= (double)t;
if (!localtime_r(&t, &tm)) { if (localtime_r(&t, &tm)) {
PyErr_SetString(InterfaceError, "failed localtime call"); res = _psyco_Timestamp(
goto exit;
}
/* Convert the tm to a wrapper containing a naive datetime.datetime */
if (!(wrapper = (pydatetimeObject *)_psyco_Timestamp(
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks, NULL))) { tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks,
goto exit; pyPsycopgTzLOCAL);
} }
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
/* Localize the datetime and assign it back to the wrapper */
if (!(dt_aware = PyObject_CallMethod(
wrapper->wrapped, "astimezone", NULL))) {
goto exit;
}
Py_CLEAR(wrapper->wrapped);
wrapper->wrapped = dt_aware;
dt_aware = NULL;
/* the wrapper is ready to be returned */
res = (PyObject *)wrapper;
wrapper = NULL;
exit:
Py_XDECREF(dt_aware);
Py_XDECREF(wrapper);
return res; return res;
} }
#endif
PyObject * PyObject *
psyco_DateFromPy(PyObject *self, PyObject *args) psyco_DateFromPy(PyObject *self, PyObject *args)
{ {

View File

@ -1,7 +1,6 @@
/* adapter_datetime.h - definition for the python date/time types /* adapter_datetime.h - definition for the python date/time types
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -46,7 +45,10 @@ typedef struct {
} pydatetimeObject; } pydatetimeObject;
RAISES_NEG HIDDEN int adapter_datetime_init(void); HIDDEN int psyco_adapter_datetime_init(void);
/* functions exported to psycopgmodule.c */
#ifdef PSYCOPG_DEFAULT_PYDATETIME
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args); HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
#define psyco_Date_doc \ #define psyco_Date_doc \
@ -84,6 +86,8 @@ HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args);
"Ticks are the number of seconds since the epoch; see the documentation " \ "Ticks are the number of seconds since the epoch; see the documentation " \
"of the standard Python time module for details)." "of the standard Python time module for details)."
#endif /* PSYCOPG_DEFAULT_PYDATETIME */
HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args); HIDDEN PyObject *psyco_DateFromPy(PyObject *module, PyObject *args);
#define psyco_DateFromPy_doc \ #define psyco_DateFromPy_doc \
"DateFromPy(datetime.date) -> new wrapper" "DateFromPy(datetime.date) -> new wrapper"

View File

@ -1,7 +1,6 @@
/* adapter_list.c - python list objects /* adapter_list.c - python list objects
* *
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -39,134 +38,55 @@ list_quote(listObject *self)
{ {
/* adapt the list by calling adapt() recursively and then wrapping /* adapt the list by calling adapt() recursively and then wrapping
everything into "ARRAY[]" */ everything into "ARRAY[]" */
PyObject *res = NULL; PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL;
PyObject **qs = NULL;
Py_ssize_t bufsize = 0;
char *buf = NULL, *ptr;
/* list consisting of only NULL don't work with the ARRAY[] construct
* so we use the {NULL,...} syntax. The same syntax is also necessary
* to convert array of arrays containing only nulls. */
int all_nulls = 1;
Py_ssize_t i, len; Py_ssize_t i, len;
len = PyList_GET_SIZE(self->wrapped); len = PyList_GET_SIZE(self->wrapped);
/* empty arrays are converted to NULLs (still searching for a way to /* empty arrays are converted to NULLs (still searching for a way to
insert an empty array in postgresql */ insert an empty array in postgresql */
if (len == 0) { if (len == 0) return Bytes_FromString("'{}'");
/* it cannot be ARRAY[] because it would make empty lists unusable
* in any() without a cast. But we may convert it into ARRAY[] below */
res = Bytes_FromString("'{}'");
goto exit;
}
if (!(qs = PyMem_New(PyObject *, len))) { tmp = PyTuple_New(len);
PyErr_NoMemory();
goto exit;
}
memset(qs, 0, len * sizeof(PyObject *));
for (i = 0; i < len; i++) { for (i=0; i<len; i++) {
PyObject *quoted;
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i); PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
if (wrapped == Py_None) { if (wrapped == Py_None) {
Py_INCREF(psyco_null); Py_INCREF(psyco_null);
qs[i] = psyco_null; quoted = psyco_null;
} }
else { else {
if (!(qs[i] = microprotocol_getquoted( quoted = microprotocol_getquoted(wrapped,
wrapped, (connectionObject*)self->connection))) { (connectionObject*)self->connection);
goto exit; if (quoted == NULL) goto error;
}
/* Lists of arrays containing only nulls are also not supported
* by the ARRAY construct so we should do some special casing */
if (PyList_Check(wrapped)) {
if (Bytes_AS_STRING(qs[i])[0] == 'A') {
all_nulls = 0;
}
else if (0 == strcmp(Bytes_AS_STRING(qs[i]), "'{}'")) {
/* case of issue #788: '{{}}' is not supported but
* array[array[]] is */
all_nulls = 0;
Py_CLEAR(qs[i]);
if (!(qs[i] = Bytes_FromString("ARRAY[]"))) {
goto exit;
}
}
}
else {
all_nulls = 0;
}
} }
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
/* here we don't loose a refcnt: SET_ITEM does not change the
reference count and we are just transferring ownership of the tmp
object to the tuple */
PyTuple_SET_ITEM(tmp, i, quoted);
} }
/* Create an array literal, usually ARRAY[...] but if the contents are /* now that we have a tuple of adapted objects we just need to join them
* all NULL or array of NULL we must use the '{...}' syntax and put "ARRAY[] around the result */
*/ str = Bytes_FromString(", ");
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) { joined = PyObject_CallMethod(str, "join", "(O)", tmp);
PyErr_NoMemory(); if (joined == NULL) goto error;
goto exit;
}
if (!all_nulls) { res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
strcpy(ptr, "ARRAY[");
ptr += 6;
for (i = 0; i < len; i++) {
Py_ssize_t sl;
sl = Bytes_GET_SIZE(qs[i]);
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
ptr += sl;
*ptr++ = ',';
}
*(ptr - 1) = ']';
}
else {
*ptr++ = '\'';
*ptr++ = '{';
for (i = 0; i < len; i++) {
/* in case all the adapted things are nulls (or array of nulls),
* the quoted string is either NULL or an array of the form
* '{NULL,...}', in which case we have to strip the extra quotes */
char *s;
Py_ssize_t sl;
s = Bytes_AS_STRING(qs[i]);
sl = Bytes_GET_SIZE(qs[i]);
if (s[0] != '\'') {
memcpy(ptr, s, sl);
ptr += sl;
}
else {
memcpy(ptr, s + 1, sl - 2);
ptr += sl - 2;
}
*ptr++ = ',';
}
*(ptr - 1) = '}';
*ptr++ = '\'';
}
res = Bytes_FromStringAndSize(buf, ptr - buf);
exit:
if (qs) {
for (i = 0; i < len; i++) {
PyObject *q = qs[i];
Py_XDECREF(q);
}
PyMem_Free(qs);
}
PyMem_Free(buf);
error:
Py_XDECREF(tmp);
Py_XDECREF(str);
Py_XDECREF(joined);
return res; return res;
} }
static PyObject * static PyObject *
list_str(listObject *self) list_str(listObject *self)
{ {
return psyco_ensure_text(list_quote(self)); return psycopg_ensure_text(list_quote(self));
} }
static PyObject * static PyObject *
@ -295,6 +215,11 @@ list_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0); return type->tp_alloc(type, 0);
} }
static PyObject *
list_repr(listObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.List object at %p>", self);
}
/* object type */ /* object type */
@ -310,7 +235,7 @@ PyTypeObject listType = {
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
0, /*tp_compare*/ 0, /*tp_compare*/
0, /*tp_repr*/ (reprfunc)list_repr, /*tp_repr*/
0, /*tp_as_number*/ 0, /*tp_as_number*/
0, /*tp_as_sequence*/ 0, /*tp_as_sequence*/
0, /*tp_as_mapping*/ 0, /*tp_as_mapping*/
@ -340,3 +265,17 @@ PyTypeObject listType = {
0, /*tp_alloc*/ 0, /*tp_alloc*/
list_new, /*tp_new*/ 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

@ -1,7 +1,6 @@
/* adapter_list.h - definition for the python list types /* adapter_list.h - definition for the python list types
* *
* Copyright (C) 2004-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2004-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -40,6 +39,10 @@ typedef struct {
PyObject *connection; PyObject *connection;
} listObject; } listObject;
HIDDEN PyObject *psyco_List(PyObject *module, PyObject *args);
#define psyco_List_doc \
"List(list, enc) -> new quoted list"
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -0,0 +1,434 @@
/* adapter_mxdatetime.c - mx date/time objects
*
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#define PSYCOPG_MODULE
#include "psycopg/psycopg.h"
#include "psycopg/adapter_mxdatetime.h"
#include "psycopg/microprotocols_proto.h"
#include <mxDateTime.h>
#include <string.h>
/* Return 0 on success, -1 on failure, but don't set an exception */
int
psyco_adapter_mxdatetime_init(void)
{
Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime init");
if (mxDateTime_ImportModuleAndAPI()) {
Dprintf("psyco_adapter_mxdatetime_init: mx.DateTime initialization failed");
PyErr_Clear();
return -1;
}
return 0;
}
/* mxdatetime_str, mxdatetime_getquoted - return result of quoting */
static PyObject *
mxdatetime_str(mxdatetimeObject *self)
{
mxDateTimeObject *dt;
mxDateTimeDeltaObject *dtd;
char buf[128] = { 0, };
switch (self->type) {
case PSYCO_MXDATETIME_DATE:
dt = (mxDateTimeObject *)self->wrapped;
if (dt->year >= 1)
PyOS_snprintf(buf, sizeof(buf) - 1, "'%04ld-%02d-%02d'::date",
dt->year, (int)dt->month, (int)dt->day);
else
PyOS_snprintf(buf, sizeof(buf) - 1, "'%04ld-%02d-%02d BC'::date",
1 - dt->year, (int)dt->month, (int)dt->day);
break;
case PSYCO_MXDATETIME_TIMESTAMP:
dt = (mxDateTimeObject *)self->wrapped;
if (dt->year >= 1)
PyOS_snprintf(buf, sizeof(buf) - 1,
"'%04ld-%02d-%02dT%02d:%02d:%09.6f'::timestamp",
dt->year, (int)dt->month, (int)dt->day,
(int)dt->hour, (int)dt->minute, dt->second);
else
PyOS_snprintf(buf, sizeof(buf) - 1,
"'%04ld-%02d-%02dT%02d:%02d:%09.6f BC'::timestamp",
1 - dt->year, (int)dt->month, (int)dt->day,
(int)dt->hour, (int)dt->minute, dt->second);
break;
case PSYCO_MXDATETIME_TIME:
case PSYCO_MXDATETIME_INTERVAL:
/* given the limitation of the mx.DateTime module that uses the same
type for both time and delta values we need to do some black magic
and make sure we're not using an adapt()ed interval as a simple
time */
dtd = (mxDateTimeDeltaObject *)self->wrapped;
if (0 <= dtd->seconds && dtd->seconds < 24*3600) {
PyOS_snprintf(buf, sizeof(buf) - 1, "'%02d:%02d:%09.6f'::time",
(int)dtd->hour, (int)dtd->minute, dtd->second);
} else {
double ss = dtd->hour*3600.0 + dtd->minute*60.0 + dtd->second;
if (dtd->seconds >= 0)
PyOS_snprintf(buf, sizeof(buf) - 1, "'%ld days %.6f seconds'::interval",
dtd->day, ss);
else
PyOS_snprintf(buf, sizeof(buf) - 1, "'-%ld days -%.6f seconds'::interval",
dtd->day, ss);
}
break;
}
return PyString_FromString(buf);
}
static PyObject *
mxdatetime_getquoted(mxdatetimeObject *self, PyObject *args)
{
return mxdatetime_str(self);
}
static PyObject *
mxdatetime_conform(mxdatetimeObject *self, PyObject *args)
{
PyObject *res, *proto;
if (!PyArg_ParseTuple(args, "O", &proto)) return NULL;
if (proto == (PyObject*)&isqlquoteType)
res = (PyObject*)self;
else
res = Py_None;
Py_INCREF(res);
return res;
}
/** the MxDateTime object **/
/* object member list */
static struct PyMemberDef mxdatetimeObject_members[] = {
{"adapted", T_OBJECT, offsetof(mxdatetimeObject, wrapped), READONLY},
{"type", T_INT, offsetof(mxdatetimeObject, type), READONLY},
{NULL}
};
/* object method table */
static PyMethodDef mxdatetimeObject_methods[] = {
{"getquoted", (PyCFunction)mxdatetime_getquoted, METH_NOARGS,
"getquoted() -> wrapped object value as SQL date/time"},
{"__conform__", (PyCFunction)mxdatetime_conform, METH_VARARGS, NULL},
{NULL} /* Sentinel */
};
/* initialization and finalization methods */
static int
mxdatetime_setup(mxdatetimeObject *self, PyObject *obj, int type)
{
Dprintf("mxdatetime_setup: init mxdatetime object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
self->type = type;
Py_INCREF(obj);
self->wrapped = obj;
Dprintf("mxdatetime_setup: good mxdatetime object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
self, Py_REFCNT(self)
);
return 0;
}
static void
mxdatetime_dealloc(PyObject* obj)
{
mxdatetimeObject *self = (mxdatetimeObject *)obj;
Py_CLEAR(self->wrapped);
Dprintf("mxdatetime_dealloc: deleted mxdatetime object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
obj, Py_REFCNT(obj)
);
Py_TYPE(obj)->tp_free(obj);
}
static int
mxdatetime_init(PyObject *obj, PyObject *args, PyObject *kwds)
{
PyObject *mx;
int type = -1; /* raise an error if type was not passed! */
if (!PyArg_ParseTuple(args, "O|i", &mx, &type))
return -1;
return mxdatetime_setup((mxdatetimeObject *)obj, mx, type);
}
static PyObject *
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 */
#define mxdatetimeType_doc \
"MxDateTime(mx, type) -> new mx.DateTime wrapper object"
PyTypeObject mxdatetimeType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.MxDateTime",
sizeof(mxdatetimeObject), 0,
mxdatetime_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)mxdatetime_repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
(reprfunc)mxdatetime_str, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
mxdatetimeType_doc, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
mxdatetimeObject_methods, /*tp_methods*/
mxdatetimeObject_members, /*tp_members*/
0, /*tp_getset*/
0, /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
mxdatetime_init, /*tp_init*/
0, /*tp_alloc*/
mxdatetime_new, /*tp_new*/
};
/** module-level functions **/
#ifdef PSYCOPG_DEFAULT_MXDATETIME
PyObject *
psyco_Date(PyObject *self, PyObject *args)
{
PyObject *res, *mx;
int year, month, day;
if (!PyArg_ParseTuple(args, "iii", &year, &month, &day))
return NULL;
mx = mxDateTime.DateTime_FromDateAndTime(year, month, day, 0, 0, 0.0);
if (mx == NULL) return NULL;
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_DATE);
Py_DECREF(mx);
return res;
}
PyObject *
psyco_Time(PyObject *self, PyObject *args)
{
PyObject *res, *mx;
int hours, minutes=0;
double seconds=0.0;
if (!PyArg_ParseTuple(args, "iid", &hours, &minutes, &seconds))
return NULL;
mx = mxDateTime.DateTimeDelta_FromTime(hours, minutes, seconds);
if (mx == NULL) return NULL;
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_TIME);
Py_DECREF(mx);
return res;
}
PyObject *
psyco_Timestamp(PyObject *self, PyObject *args)
{
PyObject *res, *mx;
int year, month, day;
int hour=0, minute=0; /* default to midnight */
double second=0.0;
if (!PyArg_ParseTuple(args, "lii|iid", &year, &month, &day,
&hour, &minute, &second))
return NULL;
mx = mxDateTime.DateTime_FromDateAndTime(year, month, day,
hour, minute, second);
if (mx == NULL) return NULL;
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_TIMESTAMP);
Py_DECREF(mx);
return res;
}
PyObject *
psyco_DateFromTicks(PyObject *self, PyObject *args)
{
PyObject *res, *mx;
double ticks;
if (!PyArg_ParseTuple(args,"d", &ticks))
return NULL;
if (!(mx = mxDateTime.DateTime_FromTicks(ticks)))
return NULL;
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_DATE);
Py_DECREF(mx);
return res;
}
PyObject *
psyco_TimeFromTicks(PyObject *self, PyObject *args)
{
PyObject *res, *mx, *dt;
double ticks;
if (!PyArg_ParseTuple(args,"d", &ticks))
return NULL;
if (!(dt = mxDateTime.DateTime_FromTicks(ticks)))
return NULL;
if (!(mx = mxDateTime.DateTimeDelta_FromDaysAndSeconds(
0, ((mxDateTimeObject*)dt)->abstime)))
{
Py_DECREF(dt);
return NULL;
}
Py_DECREF(dt);
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_TIME);
Py_DECREF(mx);
return res;
}
PyObject *
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
{
PyObject *mx, *res;
double ticks;
if (!PyArg_ParseTuple(args, "d", &ticks))
return NULL;
if (!(mx = mxDateTime.DateTime_FromTicks(ticks)))
return NULL;
res = PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_TIMESTAMP);
Py_DECREF(mx);
return res;
}
#endif
PyObject *
psyco_DateFromMx(PyObject *self, PyObject *args)
{
PyObject *mx;
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
return NULL;
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_DATE);
}
PyObject *
psyco_TimeFromMx(PyObject *self, PyObject *args)
{
PyObject *mx;
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTimeDelta_Type, &mx))
return NULL;
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_TIME);
}
PyObject *
psyco_TimestampFromMx(PyObject *self, PyObject *args)
{
PyObject *mx;
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
return NULL;
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_TIMESTAMP);
}
PyObject *
psyco_IntervalFromMx(PyObject *self, PyObject *args)
{
PyObject *mx;
if (!PyArg_ParseTuple(args, "O!", mxDateTime.DateTime_Type, &mx))
return NULL;
return PyObject_CallFunction((PyObject *)&mxdatetimeType, "Oi", mx,
PSYCO_MXDATETIME_INTERVAL);
}

View File

@ -0,0 +1,98 @@
/* adapter_mxdatetime.h - definition for the mx date/time types
*
* Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
*
* This file is part of psycopg.
*
* psycopg2 is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the copyright holders give
* permission to link this program with the OpenSSL library (or with
* modified versions of OpenSSL that use the same license as OpenSSL),
* and distribute linked combinations including the two.
*
* You must obey the GNU Lesser General Public License in all respects for
* all of the code used other than OpenSSL.
*
* psycopg2 is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*/
#ifndef PSYCOPG_MXDATETIME_H
#define PSYCOPG_MXDATETIME_H 1
#ifdef __cplusplus
extern "C" {
#endif
extern HIDDEN PyTypeObject mxdatetimeType;
typedef struct {
PyObject_HEAD
PyObject *wrapped;
int type;
#define PSYCO_MXDATETIME_TIME 0
#define PSYCO_MXDATETIME_DATE 1
#define PSYCO_MXDATETIME_TIMESTAMP 2
#define PSYCO_MXDATETIME_INTERVAL 3
} mxdatetimeObject;
/* functions exported to psycopgmodule.c */
#ifdef PSYCOPG_DEFAULT_MXDATETIME
HIDDEN PyObject *psyco_Date(PyObject *module, PyObject *args);
#define psyco_Date_doc \
"Date(year, month, day) -> new date"
HIDDEN PyObject *psyco_Time(PyObject *module, PyObject *args);
#define psyco_Time_doc \
"Time(hour, minutes, seconds) -> new time"
HIDDEN PyObject *psyco_Timestamp(PyObject *module, PyObject *args);
#define psyco_Timestamp_doc \
"Time(year, month, day, hour, minutes, seconds) -> new timestamp"
HIDDEN PyObject *psyco_DateFromTicks(PyObject *module, PyObject *args);
#define psyco_DateFromTicks_doc \
"DateFromTicks(ticks) -> new date"
HIDDEN PyObject *psyco_TimeFromTicks(PyObject *module, PyObject *args);
#define psyco_TimeFromTicks_doc \
"TimeFromTicks(ticks) -> new time"
HIDDEN PyObject *psyco_TimestampFromTicks(PyObject *module, PyObject *args);
#define psyco_TimestampFromTicks_doc \
"TimestampFromTicks(ticks) -> new timestamp"
#endif /* PSYCOPG_DEFAULT_MXDATETIME */
HIDDEN int psyco_adapter_mxdatetime_init(void);
HIDDEN PyObject *psyco_DateFromMx(PyObject *module, PyObject *args);
#define psyco_DateFromMx_doc \
"DateFromMx(mx) -> new date"
HIDDEN PyObject *psyco_TimeFromMx(PyObject *module, PyObject *args);
#define psyco_TimeFromMx_doc \
"TimeFromMx(mx) -> new time"
HIDDEN PyObject *psyco_TimestampFromMx(PyObject *module, PyObject *args);
#define psyco_TimestampFromMx_doc \
"TimestampFromMx(mx) -> new timestamp"
HIDDEN PyObject *psyco_IntervalFromMx(PyObject *module, PyObject *args);
#define psyco_IntervalFromMx_doc \
"IntervalFromMx(mx) -> new interval"
#ifdef __cplusplus
}
#endif
#endif /* !defined(PSYCOPG_MXDATETIME_H) */

View File

@ -1,7 +1,6 @@
/* adapter_pboolean.c - psycopg boolean type wrapper implementation /* adapter_pboolean.c - psycopg boolean type wrapper implementation
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -38,18 +37,27 @@
static PyObject * static PyObject *
pboolean_getquoted(pbooleanObject *self, PyObject *args) pboolean_getquoted(pbooleanObject *self, PyObject *args)
{ {
#ifdef PSYCOPG_NEW_BOOLEAN
if (PyObject_IsTrue(self->wrapped)) { if (PyObject_IsTrue(self->wrapped)) {
return Bytes_FromString("true"); return Bytes_FromString("true");
} }
else { else {
return Bytes_FromString("false"); return Bytes_FromString("false");
} }
#else
if (PyObject_IsTrue(self->wrapped)) {
return Bytes_FromString("'t'");
}
else {
return Bytes_FromString("'f'");
}
#endif
} }
static PyObject * static PyObject *
pboolean_str(pbooleanObject *self) pboolean_str(pbooleanObject *self)
{ {
return psyco_ensure_text(pboolean_getquoted(self, NULL)); return psycopg_ensure_text(pboolean_getquoted(self, NULL));
} }
static PyObject * static PyObject *
@ -138,6 +146,13 @@ pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0); return type->tp_alloc(type, 0);
} }
static PyObject *
pboolean_repr(pbooleanObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Boolean object at %p>",
self);
}
/* object type */ /* object type */
@ -146,14 +161,14 @@ pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PyTypeObject pbooleanType = { PyTypeObject pbooleanType = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2.extensions.Boolean", "psycopg2._psycopg.Boolean",
sizeof(pbooleanObject), 0, sizeof(pbooleanObject), 0,
pboolean_dealloc, /*tp_dealloc*/ pboolean_dealloc, /*tp_dealloc*/
0, /*tp_print*/ 0, /*tp_print*/
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
0, /*tp_compare*/ 0, /*tp_compare*/
0, /*tp_repr*/ (reprfunc)pboolean_repr, /*tp_repr*/
0, /*tp_as_number*/ 0, /*tp_as_number*/
0, /*tp_as_sequence*/ 0, /*tp_as_sequence*/
0, /*tp_as_mapping*/ 0, /*tp_as_mapping*/
@ -183,3 +198,17 @@ PyTypeObject pbooleanType = {
0, /*tp_alloc*/ 0, /*tp_alloc*/
pboolean_new, /*tp_new*/ 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

@ -1,7 +1,6 @@
/* adapter_pboolean.h - definition for the psycopg boolean type wrapper /* adapter_pboolean.h - definition for the psycopg boolean type wrapper
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -41,6 +40,12 @@ typedef struct {
} pbooleanObject; } 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 #ifdef __cplusplus
} }
#endif #endif

View File

@ -1,7 +1,6 @@
/* adapter_pdecimal.c - psycopg Decimal type wrapper implementation /* adapter_pdecimal.c - psycopg Decimal type wrapper implementation
* *
* Copyright (C) 2003-2019 Federico Di Gregorio <fog@debian.org> * Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
* Copyright (C) 2020-2021 The Psycopg Team
* *
* This file is part of psycopg. * This file is part of psycopg.
* *
@ -81,7 +80,8 @@ pdecimal_getquoted(pdecimalObject *self, PyObject *args)
/* res may be unicode and may suffer for issue #57 */ /* res may be unicode and may suffer for issue #57 */
output: output:
/* unicode to bytes */ #if PY_MAJOR_VERSION > 2
/* unicode to bytes in Py3 */
{ {
PyObject *tmp = PyUnicode_AsUTF8String(res); PyObject *tmp = PyUnicode_AsUTF8String(res);
Py_DECREF(res); Py_DECREF(res);
@ -89,6 +89,7 @@ output:
goto end; goto end;
} }
} }
#endif
if ('-' == Bytes_AS_STRING(res)[0]) { if ('-' == Bytes_AS_STRING(res)[0]) {
/* Prepend a space in front of negative numbers (ticket #57) */ /* Prepend a space in front of negative numbers (ticket #57) */
@ -112,7 +113,7 @@ end:
static PyObject * static PyObject *
pdecimal_str(pdecimalObject *self) pdecimal_str(pdecimalObject *self)
{ {
return psyco_ensure_text(pdecimal_getquoted(self, NULL)); return psycopg_ensure_text(pdecimal_getquoted(self, NULL));
} }
static PyObject * static PyObject *
@ -201,6 +202,13 @@ pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0); return type->tp_alloc(type, 0);
} }
static PyObject *
pdecimal_repr(pdecimalObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Decimal object at %p>",
self);
}
/* object type */ /* object type */
@ -216,7 +224,7 @@ PyTypeObject pdecimalType = {
0, /*tp_getattr*/ 0, /*tp_getattr*/
0, /*tp_setattr*/ 0, /*tp_setattr*/
0, /*tp_compare*/ 0, /*tp_compare*/
0, /*tp_repr*/ (reprfunc)pdecimal_repr, /*tp_repr*/
0, /*tp_as_number*/ 0, /*tp_as_number*/
0, /*tp_as_sequence*/ 0, /*tp_as_sequence*/
0, /*tp_as_mapping*/ 0, /*tp_as_mapping*/
@ -246,3 +254,17 @@ PyTypeObject pdecimalType = {
0, /*tp_alloc*/ 0, /*tp_alloc*/
pdecimal_new, /*tp_new*/ 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);
}

Some files were not shown because too many files have changed in this diff Show More