mirror of
https://github.com/psycopg/psycopg2.git
synced 2024-11-29 04:13:43 +03:00
Merge remote branch 'upstream/devel' into devel
Conflicts: setup.py tests/__init__.py tests/testconfig.py
This commit is contained in:
commit
3fc4dcec06
|
@ -89,14 +89,33 @@ by the `psycopg2.extensions.adapt()` function.
|
|||
The `~cursor.execute()` method adapts its arguments to the
|
||||
`~psycopg2.extensions.ISQLQuote` protocol. Objects that conform to this
|
||||
protocol expose a `!getquoted()` method returning the SQL representation
|
||||
of the object as a string.
|
||||
of the object as a string (the method must return `!bytes` in Python 3).
|
||||
Optionally the conform object may expose a
|
||||
`~psycopg2.extensions.ISQLQuote.prepare()` method.
|
||||
|
||||
The easiest way to adapt an object to an SQL string is to register an adapter
|
||||
function via the `~psycopg2.extensions.register_adapter()` function. The
|
||||
adapter function must take the value to be adapted as argument and return a
|
||||
conform object. A convenient object is the `~psycopg2.extensions.AsIs`
|
||||
wrapper, whose `!getquoted()` result is simply the `!str()`\ ing
|
||||
conversion of the wrapped object.
|
||||
There are two basic ways to have a Python object adapted to SQL:
|
||||
|
||||
- the object itself is conform, or knows how to make itself conform. Such
|
||||
object must expose a `__conform__()` method that will be called with the
|
||||
protocol object as argument. The object can check that the protocol is
|
||||
`!ISQLQuote`, in which case it can return `!self` (if the object also
|
||||
implements `!getquoted()`) or a suitable wrapper object. This option is
|
||||
viable if you are the author of the object and if the object is specifically
|
||||
designed for the database (i.e. having Psycopg as a dependency and polluting
|
||||
its interface with the required methods doesn't bother you). For a simple
|
||||
example you can take a look to the source code for the
|
||||
`psycopg2.extras.Inet` object.
|
||||
|
||||
- If implementing the `!ISQLQuote` interface directly in the object is not an
|
||||
option, you can use an adaptation function, taking the object to be adapted
|
||||
as argument and returning a conforming object. The adapter must be
|
||||
registered via the `~psycopg2.extensions.register_adapter()` function. A
|
||||
simple example wrapper is the `!psycopg2.extras.UUID_adapter` used by the
|
||||
`~psycopg2.extras.register_uuid()` function.
|
||||
|
||||
A convenient object to write adapters is the `~psycopg2.extensions.AsIs`
|
||||
wrapper, whose `!getquoted()` result is simply the `!str()`\ ing conversion of
|
||||
the wrapped object.
|
||||
|
||||
.. index::
|
||||
single: Example; Types adaptation
|
||||
|
|
|
@ -189,8 +189,9 @@ deal with Python objects adaptation:
|
|||
.. method:: getquoted()
|
||||
|
||||
Subclasses or other conforming objects should return a valid SQL
|
||||
string representing the wrapped object. The `!ISQLQuote`
|
||||
implementation does nothing.
|
||||
string representing the wrapped object. In Python 3 the SQL must be
|
||||
returned in a `!bytes` object. The `!ISQLQuote` implementation does
|
||||
nothing.
|
||||
|
||||
.. method:: prepare(conn)
|
||||
|
||||
|
|
|
@ -94,6 +94,18 @@ Psycopg converts :sql:`decimal`\/\ :sql:`numeric` database types into Python `!D
|
|||
documentation. If you find `!psycopg2.extensions.DECIMAL` not avalable, use
|
||||
`!psycopg2._psycopg.DECIMAL` instead.
|
||||
|
||||
Transferring binary data from PostgreSQL 9.0 doesn't work.
|
||||
PostgreSQL 9.0 uses by default `the "hex" format`__ to transfer
|
||||
:sql:`bytea` data: the format can't be parsed by the libpq 8.4 and
|
||||
earlier. Three options to solve the problem are:
|
||||
|
||||
- set the bytea_output__ parameter to ``escape`` in the server;
|
||||
- use ``SET bytea_output TO escape`` in the client before reading binary
|
||||
data;
|
||||
- upgrade the libpq library on the client to at least 9.0.
|
||||
|
||||
.. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html
|
||||
.. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
||||
|
||||
Best practices
|
||||
--------------
|
||||
|
|
|
@ -233,15 +233,37 @@ the SQL string that would be sent to the database.
|
|||
.. index::
|
||||
pair: Strings; Adaptation
|
||||
single: Unicode; Adaptation
|
||||
|
||||
- String types: `!str`, `!unicode` are converted in SQL string syntax.
|
||||
`!unicode` objects (`!str` in Python 3) are encoded in the connection
|
||||
`~connection.encoding` to be sent to the backend: trying to send a character
|
||||
not supported by the encoding will result in an error. Received data can be
|
||||
converted either as `!str` or `!unicode`: see :ref:`unicode-handling` for
|
||||
received, either `!str` or `!unicode`
|
||||
|
||||
.. index::
|
||||
single: Buffer; Adaptation
|
||||
single: bytea; Adaptation
|
||||
single: Binary string
|
||||
|
||||
- String types: `!str`, `!unicode` are converted in SQL string
|
||||
syntax. `!buffer` is converted in PostgreSQL binary string syntax,
|
||||
suitable for :sql:`bytea` fields. When reading textual fields, either
|
||||
`!str` or `!unicode` can be received: see
|
||||
:ref:`unicode-handling`.
|
||||
- Binary types: Python types such as `!bytes`, `!bytearray`, `!buffer`,
|
||||
`!memoryview` are converted in PostgreSQL binary string syntax, suitable for
|
||||
:sql:`bytea` fields. Received data is returned as `!buffer` (in Python 2) or
|
||||
`!memoryview` (in Python 3).
|
||||
|
||||
.. warning::
|
||||
|
||||
PostgreSQL 9 uses by default `a new "hex" format`__ to emit :sql:`bytea`
|
||||
fields. Unfortunately this format can't be parsed by libpq versions
|
||||
before 9.0. This means that using a library client with version lesser
|
||||
than 9.0 to talk with a server 9.0 or later you may have problems
|
||||
receiving :sql:`bytea` data. To work around this problem you can set the
|
||||
`bytea_output`__ parameter to ``escape``, either in the server
|
||||
configuration or in the client session using a query such as ``SET
|
||||
bytea_output TO escape;`` before trying to receive binary data.
|
||||
|
||||
.. __: http://www.postgresql.org/docs/9.0/static/datatype-binary.html
|
||||
.. __: http://www.postgresql.org/docs/9.0/static/runtime-config-client.html#GUC-BYTEA-OUTPUT
|
||||
|
||||
.. index::
|
||||
single: Adaptation; Date/Time objects
|
||||
|
@ -338,8 +360,8 @@ defined on the database connection (the `PostgreSQL encoding`__, available in
|
|||
.. __: http://www.postgresql.org/docs/9.0/static/multibyte.html
|
||||
.. __: http://docs.python.org/library/codecs.html#standard-encodings
|
||||
|
||||
When reading data from the database, the strings returned are usually 8 bit
|
||||
`!str` objects encoded in the database client encoding::
|
||||
When reading data from the database, in Python 2 the strings returned are
|
||||
usually 8 bit `!str` objects encoded in the database client encoding::
|
||||
|
||||
>>> print conn.encoding
|
||||
UTF8
|
||||
|
@ -356,9 +378,10 @@ When reading data from the database, the strings returned are usually 8 bit
|
|||
>>> print type(x), repr(x)
|
||||
<type 'str'> '\xe0\xe8\xec\xf2\xf9\xa4'
|
||||
|
||||
In order to obtain `!unicode` objects instead, it is possible to
|
||||
register a typecaster so that PostgreSQL textual types are automatically
|
||||
*decoded* using the current client encoding::
|
||||
In Python 3 instead the strings are automatically *decoded* in the connection
|
||||
`~connection.encoding`, as the `!str` object can represent Unicode characters.
|
||||
In Python 2 you must register a :ref:`typecaster
|
||||
<type-casting-from-sql-to-python>` in order to receive `!unicode` objects::
|
||||
|
||||
>>> psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, cur)
|
||||
|
||||
|
@ -375,9 +398,9 @@ the connection or globally: see the function
|
|||
|
||||
.. note::
|
||||
|
||||
If you want to receive uniformly all your database input in Unicode, you
|
||||
can register the related typecasters globally as soon as Psycopg is
|
||||
imported::
|
||||
In Python 2, if you want to receive uniformly all your database input in
|
||||
Unicode, you can register the related typecasters globally as soon as
|
||||
Psycopg is imported::
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
|
|
|
@ -835,14 +835,22 @@ class CompositeCaster(object):
|
|||
# Store the transaction status of the connection to revert it after use
|
||||
conn_status = conn.status
|
||||
|
||||
# Use the correct schema
|
||||
if '.' in name:
|
||||
schema, tname = name.split('.', 1)
|
||||
else:
|
||||
tname = name
|
||||
schema = 'public'
|
||||
|
||||
# get the type oid and attributes
|
||||
curs.execute("""\
|
||||
SELECT t.oid, attname, atttypid
|
||||
FROM pg_type t
|
||||
JOIN pg_namespace ns ON typnamespace = ns.oid
|
||||
JOIN pg_attribute a ON attrelid = typrelid
|
||||
WHERE typname = %s and nspname = 'public';
|
||||
""", (name, ))
|
||||
WHERE typname = %s and nspname = %s
|
||||
ORDER BY attnum;
|
||||
""", (tname, schema))
|
||||
|
||||
recs = curs.fetchall()
|
||||
|
||||
|
@ -858,7 +866,7 @@ WHERE typname = %s and nspname = 'public';
|
|||
type_oid = recs[0][0]
|
||||
type_attrs = [ (r[1], r[2]) for r in recs ]
|
||||
|
||||
return CompositeCaster(name, type_oid, type_attrs)
|
||||
return CompositeCaster(tname, type_oid, type_attrs)
|
||||
|
||||
def register_composite(name, conn_or_curs, globally=False):
|
||||
"""Register a typecaster to convert a composite type into a tuple.
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
# Configure the test suite from the env variables.
|
||||
|
||||
import os
|
||||
|
||||
dbname = os.environ.get('PSYCOPG2_TESTDB', 'psycopg2_test')
|
||||
dbhost = os.environ.get('PSYCOPG2_TESTDB_HOST', None)
|
||||
dbport = os.environ.get('PSYCOPG2_TESTDB_PORT', None)
|
||||
dbuser = os.environ.get('PSYCOPG2_TESTDB_USER', None)
|
||||
dbpass = os.environ.get('PSYCOPG2_TESTDB_PASSWORD', None)
|
||||
|
||||
# Check if we want to test psycopg's green path.
|
||||
green = os.environ.get('PSYCOPG2_TEST_GREEN', None)
|
||||
if green:
|
||||
if green == '1':
|
||||
from psycopg2.extras import wait_select as wait_callback
|
||||
elif green == 'eventlet':
|
||||
from eventlet.support.psycopg2_patcher import eventlet_wait_callback \
|
||||
as wait_callback
|
||||
else:
|
||||
raise ValueError("please set 'PSYCOPG2_TEST_GREEN' to a valid value")
|
||||
|
||||
import psycopg2.extensions
|
||||
psycopg2.extensions.set_wait_callback(wait_callback)
|
||||
|
||||
# Construct a DSN to connect to the test database:
|
||||
dsn = 'dbname=%s' % dbname
|
||||
if dbhost is not None:
|
||||
dsn += ' host=%s' % dbhost
|
||||
if dbport is not None:
|
||||
dsn += ' port=%s' % dbport
|
||||
if dbuser is not None:
|
||||
dsn += ' user=%s' % dbuser
|
||||
if dbpass is not None:
|
||||
dsn += ' password=%s' % dbpass
|
||||
|
||||
|
||||
# Configure the test suite from the env variables.
|
||||
|
||||
import os
|
||||
|
||||
dbname = os.environ.get('PSYCOPG2_TESTDB', 'psycopg2_test')
|
||||
dbhost = os.environ.get('PSYCOPG2_TESTDB_HOST', None)
|
||||
dbport = os.environ.get('PSYCOPG2_TESTDB_PORT', None)
|
||||
dbuser = os.environ.get('PSYCOPG2_TESTDB_USER', None)
|
||||
dbpass = os.environ.get('PSYCOPG2_TESTDB_PASSWORD', None)
|
||||
|
||||
# Check if we want to test psycopg's green path.
|
||||
green = os.environ.get('PSYCOPG2_TEST_GREEN', None)
|
||||
if green:
|
||||
if green == '1':
|
||||
from psycopg2.extras import wait_select as wait_callback
|
||||
elif green == 'eventlet':
|
||||
from eventlet.support.psycopg2_patcher import eventlet_wait_callback \
|
||||
as wait_callback
|
||||
else:
|
||||
raise ValueError("please set 'PSYCOPG2_TEST_GREEN' to a valid value")
|
||||
|
||||
import psycopg2.extensions
|
||||
psycopg2.extensions.set_wait_callback(wait_callback)
|
||||
|
||||
# Construct a DSN to connect to the test database:
|
||||
dsn = 'dbname=%s' % dbname
|
||||
if dbhost is not None:
|
||||
dsn += ' host=%s' % dbhost
|
||||
if dbport is not None:
|
||||
dsn += ' port=%s' % dbport
|
||||
if dbuser is not None:
|
||||
dsn += ' user=%s' % dbuser
|
||||
if dbpass is not None:
|
||||
dsn += ' password=%s' % dbpass
|
||||
|
||||
|
||||
|
|
|
@ -525,6 +525,24 @@ class AdaptTypeTestCase(unittest.TestCase):
|
|||
conn1.close()
|
||||
conn2.close()
|
||||
|
||||
@skip_if_no_composite
|
||||
def test_composite_namespace(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("""
|
||||
select nspname from pg_namespace
|
||||
where nspname = 'typens';
|
||||
""")
|
||||
if not curs.fetchone():
|
||||
curs.execute("create schema typens;")
|
||||
self.conn.commit()
|
||||
|
||||
self._create_type("typens.typens_ii",
|
||||
[("a", "integer"), ("b", "integer")])
|
||||
t = psycopg2.extras.register_composite(
|
||||
"typens.typens_ii", self.conn)
|
||||
curs.execute("select (4,8)::typens.typens_ii")
|
||||
self.assertEqual(curs.fetchone()[0], (4,8))
|
||||
|
||||
def _create_type(self, name, fields):
|
||||
curs = self.conn.cursor()
|
||||
try:
|
||||
|
@ -534,11 +552,16 @@ class AdaptTypeTestCase(unittest.TestCase):
|
|||
|
||||
curs.execute("create type %s as (%s);" % (name,
|
||||
", ".join(["%s %s" % p for p in fields])))
|
||||
if '.' in name:
|
||||
schema, name = name.split('.')
|
||||
else:
|
||||
schema = 'public'
|
||||
|
||||
curs.execute("""\
|
||||
SELECT t.oid
|
||||
FROM pg_type t JOIN pg_namespace ns ON typnamespace = ns.oid
|
||||
WHERE typname = %s and nspname = 'public';
|
||||
""", (name,))
|
||||
WHERE typname = %s and nspname = %s;
|
||||
""", (name, schema))
|
||||
oid = curs.fetchone()[0]
|
||||
self.conn.commit()
|
||||
return oid
|
||||
|
|
Loading…
Reference in New Issue
Block a user