psycopg2/lib/extensions.py
2018-09-22 19:02:33 -07:00

224 lines
6.9 KiB
Python

"""psycopg extensions to the DBAPI-2.0
This module holds all the extensions to the DBAPI-2.0 provided by psycopg.
- `connection` -- the new-type inheritable connection class
- `cursor` -- the new-type inheritable cursor class
- `lobject` -- the new-type inheritable large object class
- `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used
by psycopg to adapt Python types to PostgreSQL ones
.. _PEP-246: https://www.python.org/dev/peps/pep-0246/
"""
# psycopg/extensions.py - DBAPI-2.0 extensions specific to psycopg
#
# 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 re as _re
from psycopg2._psycopg import ( # noqa
BINARYARRAY, BOOLEAN, BOOLEANARRAY, DATE, DATEARRAY, DATETIMEARRAY,
DECIMAL, DECIMALARRAY, FLOAT, FLOATARRAY, INTEGER, INTEGERARRAY,
INTERVAL, INTERVALARRAY, LONGINTEGER, LONGINTEGERARRAY, ROWIDARRAY,
STRINGARRAY, TIME, TIMEARRAY, UNICODE, UNICODEARRAY,
AsIs, Binary, Boolean, Float, Int, QuotedString, )
try:
from psycopg2._psycopg import ( # noqa
MXDATE, MXDATETIME, MXDATETIMETZ, MXINTERVAL, MXTIME, MXDATEARRAY,
MXDATETIMEARRAY, MXDATETIMETZARRAY, MXINTERVALARRAY, MXTIMEARRAY,
DateFromMx, TimeFromMx, TimestampFromMx, IntervalFromMx, )
except ImportError:
pass
try:
from psycopg2._psycopg import ( # noqa
PYDATE, PYDATETIME, PYDATETIMETZ, PYINTERVAL, PYTIME, PYDATEARRAY,
PYDATETIMEARRAY, PYDATETIMETZARRAY, PYINTERVALARRAY, PYTIMEARRAY,
DateFromPy, TimeFromPy, TimestampFromPy, IntervalFromPy, )
except ImportError:
pass
from psycopg2._psycopg import ( # noqa
adapt, adapters, encodings, connection, cursor,
lobject, Xid, libpq_version, parse_dsn, quote_ident,
string_types, binary_types, new_type, new_array_type, register_type,
ISQLQuote, Notify, Diagnostics, Column,
QueryCanceledError, TransactionRollbackError,
set_wait_callback, get_wait_callback, encrypt_password, )
"""Isolation level values."""
ISOLATION_LEVEL_AUTOCOMMIT = 0
ISOLATION_LEVEL_READ_UNCOMMITTED = 4
ISOLATION_LEVEL_READ_COMMITTED = 1
ISOLATION_LEVEL_REPEATABLE_READ = 2
ISOLATION_LEVEL_SERIALIZABLE = 3
ISOLATION_LEVEL_DEFAULT = None
"""psycopg connection status values."""
STATUS_SETUP = 0
STATUS_READY = 1
STATUS_BEGIN = 2
STATUS_SYNC = 3 # currently unused
STATUS_ASYNC = 4 # currently unused
STATUS_PREPARED = 5
# This is a useful mnemonic to check if the connection is in a transaction
STATUS_IN_TRANSACTION = STATUS_BEGIN
"""psycopg asynchronous connection polling values"""
POLL_OK = 0
POLL_READ = 1
POLL_WRITE = 2
POLL_ERROR = 3
"""Backend transaction status values."""
TRANSACTION_STATUS_IDLE = 0
TRANSACTION_STATUS_ACTIVE = 1
TRANSACTION_STATUS_INTRANS = 2
TRANSACTION_STATUS_INERROR = 3
TRANSACTION_STATUS_UNKNOWN = 4
def register_adapter(typ, callable):
"""Register 'callable' as an ISQLQuote adapter for type 'typ'."""
adapters[(typ, ISQLQuote)] = callable
# The SQL_IN class is the official adapter for tuples starting from 2.0.6.
class SQL_IN(object):
"""Adapt any iterable to an SQL quotable object."""
def __init__(self, seq):
self._seq = seq
self._conn = None
def prepare(self, conn):
self._conn = conn
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
pobjs = [adapt(o) for o in self._seq]
if self._conn is not None:
for obj in pobjs:
if hasattr(obj, 'prepare'):
obj.prepare(self._conn)
qobjs = [o.getquoted() for o in pobjs]
return b'(' + b', '.join(qobjs) + b')'
def __str__(self):
return str(self.getquoted())
class NoneAdapter(object):
"""Adapt None to NULL.
This adapter is not used normally as a fast path in mogrify uses NULL,
but it makes easier to adapt composite types.
"""
def __init__(self, obj):
pass
def getquoted(self, _null=b"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(["%s=%s" % (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
from psycopg2._json import register_default_json, register_default_jsonb # noqa
try:
JSON, JSONARRAY = register_default_json()
JSONB, JSONBARRAY = register_default_jsonb()
except ImportError:
pass
del register_default_json, register_default_jsonb
# Create default Range typecasters
from psycopg2. _range import Range # noqa
del Range
# Add the "cleaned" version of the encodings to the key.
# When the encoding is set its name is cleaned up from - and _ and turned
# uppercase, so an encoding not respecting these rules wouldn't be found in the
# encodings keys and would raise an exception with the unicode typecaster
for k, v in list(encodings.items()):
k = k.replace('_', '').replace('-', '').upper()
encodings[k] = v
del k, v