Merge remote branch 'upstream/devel' into devel

Conflicts:
	setup.py
	tests/__init__.py
	tests/testconfig.py
This commit is contained in:
Jason Erickson 2011-02-10 16:07:38 -07:00
commit 3fc4dcec06
7 changed files with 149 additions and 63 deletions

View File

@ -89,14 +89,33 @@ by the `psycopg2.extensions.adapt()` function.
The `~cursor.execute()` method adapts its arguments to the The `~cursor.execute()` method adapts its arguments to the
`~psycopg2.extensions.ISQLQuote` protocol. Objects that conform to this `~psycopg2.extensions.ISQLQuote` protocol. Objects that conform to this
protocol expose a `!getquoted()` method returning the SQL representation 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 There are two basic ways to have a Python object adapted to SQL:
function via the `~psycopg2.extensions.register_adapter()` function. The
adapter function must take the value to be adapted as argument and return a - the object itself is conform, or knows how to make itself conform. Such
conform object. A convenient object is the `~psycopg2.extensions.AsIs` object must expose a `__conform__()` method that will be called with the
wrapper, whose `!getquoted()` result is simply the `!str()`\ ing protocol object as argument. The object can check that the protocol is
conversion of the wrapped object. `!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:: .. index::
single: Example; Types adaptation single: Example; Types adaptation

View File

@ -189,8 +189,9 @@ deal with Python objects adaptation:
.. method:: getquoted() .. method:: getquoted()
Subclasses or other conforming objects should return a valid SQL Subclasses or other conforming objects should return a valid SQL
string representing the wrapped object. The `!ISQLQuote` string representing the wrapped object. In Python 3 the SQL must be
implementation does nothing. returned in a `!bytes` object. The `!ISQLQuote` implementation does
nothing.
.. method:: prepare(conn) .. method:: prepare(conn)

View File

@ -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 documentation. If you find `!psycopg2.extensions.DECIMAL` not avalable, use
`!psycopg2._psycopg.DECIMAL` instead. `!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 Best practices
-------------- --------------

View File

@ -233,15 +233,37 @@ the SQL string that would be sent to the database.
.. index:: .. index::
pair: Strings; Adaptation pair: Strings; Adaptation
single: Unicode; 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: Buffer; Adaptation
single: bytea; Adaptation single: bytea; Adaptation
single: Binary string single: Binary string
- String types: `!str`, `!unicode` are converted in SQL string - Binary types: Python types such as `!bytes`, `!bytearray`, `!buffer`,
syntax. `!buffer` is converted in PostgreSQL binary string syntax, `!memoryview` are converted in PostgreSQL binary string syntax, suitable for
suitable for :sql:`bytea` fields. When reading textual fields, either :sql:`bytea` fields. Received data is returned as `!buffer` (in Python 2) or
`!str` or `!unicode` can be received: see `!memoryview` (in Python 3).
:ref:`unicode-handling`.
.. 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:: .. index::
single: Adaptation; Date/Time objects 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://www.postgresql.org/docs/9.0/static/multibyte.html
.. __: http://docs.python.org/library/codecs.html#standard-encodings .. __: http://docs.python.org/library/codecs.html#standard-encodings
When reading data from the database, the strings returned are usually 8 bit When reading data from the database, in Python 2 the strings returned are
`!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
@ -356,9 +378,10 @@ When reading data from the database, the strings returned are usually 8 bit
>>> 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 order to obtain `!unicode` objects instead, it is possible to In Python 3 instead the strings are automatically *decoded* in the connection
register a typecaster so that PostgreSQL textual types are automatically `~connection.encoding`, as the `!str` object can represent Unicode characters.
*decoded* using the current client encoding:: 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) >>> psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, cur)
@ -375,9 +398,9 @@ the connection or globally: see the function
.. note:: .. note::
If you want to receive uniformly all your database input in Unicode, you In Python 2, if you want to receive uniformly all your database input in
can register the related typecasters globally as soon as Psycopg is Unicode, you can register the related typecasters globally as soon as
imported:: Psycopg is imported::
import psycopg2 import psycopg2
import psycopg2.extensions import psycopg2.extensions

View File

@ -835,14 +835,22 @@ class CompositeCaster(object):
# 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
# Use the correct schema
if '.' in name:
schema, tname = name.split('.', 1)
else:
tname = name
schema = 'public'
# get the type oid and attributes # get the type oid and attributes
curs.execute("""\ curs.execute("""\
SELECT t.oid, attname, atttypid SELECT t.oid, attname, atttypid
FROM pg_type t FROM pg_type t
JOIN pg_namespace ns ON typnamespace = ns.oid JOIN pg_namespace ns ON typnamespace = ns.oid
JOIN pg_attribute a ON attrelid = typrelid JOIN pg_attribute a ON attrelid = typrelid
WHERE typname = %s and nspname = 'public'; WHERE typname = %s and nspname = %s
""", (name, )) ORDER BY attnum;
""", (tname, schema))
recs = curs.fetchall() recs = curs.fetchall()
@ -858,7 +866,7 @@ WHERE typname = %s and nspname = 'public';
type_oid = recs[0][0] type_oid = recs[0][0]
type_attrs = [ (r[1], r[2]) for r in recs ] 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): def register_composite(name, conn_or_curs, globally=False):
"""Register a typecaster to convert a composite type into a tuple. """Register a typecaster to convert a composite type into a tuple.

View File

@ -525,6 +525,24 @@ class AdaptTypeTestCase(unittest.TestCase):
conn1.close() conn1.close()
conn2.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): def _create_type(self, name, fields):
curs = self.conn.cursor() curs = self.conn.cursor()
try: try:
@ -534,11 +552,16 @@ class AdaptTypeTestCase(unittest.TestCase):
curs.execute("create type %s as (%s);" % (name, curs.execute("create type %s as (%s);" % (name,
", ".join(["%s %s" % p for p in fields]))) ", ".join(["%s %s" % p for p in fields])))
if '.' in name:
schema, name = name.split('.')
else:
schema = 'public'
curs.execute("""\ curs.execute("""\
SELECT t.oid SELECT t.oid
FROM pg_type t JOIN pg_namespace ns ON typnamespace = ns.oid FROM pg_type t JOIN pg_namespace ns ON typnamespace = ns.oid
WHERE typname = %s and nspname = 'public'; WHERE typname = %s and nspname = %s;
""", (name,)) """, (name, schema))
oid = curs.fetchone()[0] oid = curs.fetchone()[0]
self.conn.commit() self.conn.commit()
return oid return oid