The documentation is now mostly doctest-able

This commit is contained in:
Jonathan Ballet 2010-02-13 17:06:39 +01:00 committed by Daniele Varrazzo
parent 67de65040c
commit 323340abc6
8 changed files with 166 additions and 87 deletions

View File

@ -10,6 +10,13 @@ More advanced topics
.. _subclassing-connection: .. _subclassing-connection:
.. _subclassing-cursor: .. _subclassing-cursor:
.. testsetup:: *
import re
cur.execute("CREATE TABLE atable (apoint point)")
conn.commit()
Connection and cursor factories Connection and cursor factories
------------------------------- -------------------------------
@ -42,8 +49,8 @@ An example of cursor subclass performing logging is::
raise raise
conn = psycopg2.connect(DSN) conn = psycopg2.connect(DSN)
curs = conn.cursor(cursor_factory=LoggingCursor) cur = conn.cursor(cursor_factory=LoggingCursor)
curs.execute("INSERT INTO mytable VALUES (%s, %s, %s);", cur.execute("INSERT INTO mytable VALUES (%s, %s, %s);",
(10, 20, 30)) (10, 20, 30))
@ -78,22 +85,24 @@ conversion of the wrapped object.
single: Example; Types adaptation single: Example; Types adaptation
Example: mapping of a :class:`!Point` class into the |point|_ PostgreSQL Example: mapping of a :class:`!Point` class into the |point|_ PostgreSQL
geometric type:: geometric type:
from psycopg2.extensions import adapt, register_adapter, AsIs .. doctest::
class Point(object): >>> from psycopg2.extensions import adapt, register_adapter, AsIs
def __init__(self, x, y):
self.x = x
self.y = y
def adapt_point(point): >>> class Point(object):
return AsIs("'(%s, %s)'" % (adapt(point.x), adapt(point.y))) ... def __init__(self, x, y):
... self.x = x
... self.y = y
register_adapter(Point, adapt_point) >>> def adapt_point(point):
... return AsIs("'(%s, %s)'" % (adapt(point.x), adapt(point.y)))
curs.execute("INSERT INTO atable (apoint) VALUES (%s)", >>> register_adapter(Point, adapt_point)
(Point(1.23, 4.56),))
>>> cur.execute("INSERT INTO atable (apoint) VALUES (%s)",
... (Point(1.23, 4.56),))
.. |point| replace:: :sql:`point` .. |point| replace:: :sql:`point`
@ -117,55 +126,54 @@ through an user-defined adapting function. An adapter function takes two
arguments: the object string representation as returned by PostgreSQL and the arguments: the object string representation as returned by PostgreSQL and the
cursor currently being read, and should return a new Python object. For cursor currently being read, and should return a new Python object. For
example, the following function parses the PostgreSQL :sql:`point` example, the following function parses the PostgreSQL :sql:`point`
representation into the previously defined :class:`!Point` class:: representation into the previously defined :class:`!Point` class:
def cast_point(value, curs): >>> def cast_point(value, cur):
if value is None: ... if value is None:
return None ... return None
...
# Convert from (f1, f2) syntax using a regular expression. ... # Convert from (f1, f2) syntax using a regular expression.
m = re.match(r"\(([^)]+),([^)]+)\)", value) ... m = re.match(r"\(([^)]+),([^)]+)\)", value)
if m: ... if m:
return Point(float(m.group(1)), float(m.group(2))) ... return Point(float(m.group(1)), float(m.group(2)))
else: ... else:
raise InterfaceError("bad point representation: %r" % value) ... raise InterfaceError("bad point representation: %r" % value)
In order to create a mapping from a PostgreSQL type (either standard or In order to create a mapping from a PostgreSQL type (either standard or
user-defined), its OID must be known. It can be retrieved either by the second user-defined), its OID must be known. It can be retrieved either by the second
column of the :attr:`cursor.description`:: column of the :attr:`cursor.description`:
curs.execute("SELECT NULL::point") >>> cur.execute("SELECT NULL::point")
point_oid = curs.description[0][1] # usually returns 600 >>> point_oid = cur.description[0][1] # usually returns 600
or by querying the system catalogs for the type name and namespace (the or by querying the system catalogs for the type name and namespace (the
namespace for system objects is :sql:`pg_catalog`):: namespace for system objects is :sql:`pg_catalog`):
curs.execute(""" >>> cur.execute("""
SELECT pg_type.oid ... SELECT pg_type.oid
FROM pg_type JOIN pg_namespace ... FROM pg_type JOIN pg_namespace
ON typnamespace = pg_namespace.oid ... ON typnamespace = pg_namespace.oid
WHERE typname = %(typename)s ... WHERE typname = %(typename)s
AND nspname = %(namespace)s""", ... AND nspname = %(namespace)s""",
{'typename': 'point', 'namespace': 'pg_catalog'}) ... {'typename': 'point', 'namespace': 'pg_catalog'})
>>> point_oid = cur.fetchone()[0]
point_oid = curs.fetchone()[0] After you know the object OID, you must can and register the new type:
After you know the object OID, you must can and register the new type:: >>> POINT = psycopg2.extensions.new_type((point_oid,), "POINT", cast_point)
>>> psycopg2.extensions.register_type(POINT)
POINT = psycopg2.extensions.new_type((point_oid,), "POINT", cast_point)
psycopg2.extensions.register_type(POINT)
The :func:`~psycopg2.extensions.new_type` function binds the object OIDs The :func:`~psycopg2.extensions.new_type` function binds the object OIDs
(more than one can be specified) to the adapter function. (more than one can be specified) to the adapter function.
:func:`~psycopg2.extensions.register_type` completes the spell. Conversion :func:`~psycopg2.extensions.register_type` completes the spell. Conversion
is automatically performed when a column whose type is a registered OID is is automatically performed when a column whose type is a registered OID is
read:: read:
>>> curs.execute("SELECT '(10.2,20.3)'::point") >>> cur.execute("SELECT '(10.2,20.3)'::point")
>>> point = curs.fetchone()[0] >>> point = cur.fetchone()[0]
>>> print type(point), point.x, point.y >>> print type(point), point.x, point.y
<class '__main__.Point'> 10.2 20.3 <class 'Point'> 10.2 20.3
@ -318,3 +326,11 @@ call::
print row print row
.. testcode::
:hide:
conn.rollback()
cur.execute("DROP TABLE atable")
conn.commit()
cur.close()
conn.close()

View File

@ -22,7 +22,8 @@ sys.path.append(os.path.abspath('.'))
# 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 = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.ifconfig' ] extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.ifconfig',
'sphinx.ext.doctest']
# Specific extensions for Psycopg documentation. # Specific extensions for Psycopg documentation.
extensions += [ 'dbapi_extension', 'sql_role' ] extensions += [ 'dbapi_extension', 'sql_role' ]
@ -222,3 +223,31 @@ latex_documents = [
# If false, no module index is generated. # If false, no module index is generated.
#latex_use_modindex = True #latex_use_modindex = True
doctest_global_setup = """
import os
import psycopg2
def test_connect():
try:
dsn = os.environ['PSYCOPG2_DSN']
except KeyError:
assert False, "You need to set the environment variable PSYCOPG2_DSN" \
" in order to test the documentation!"
return psycopg2.connect(dsn)
conn = test_connect()
cur = conn.cursor()
def create_test_table():
try:
cur.execute("CREATE TABLE test (id SERIAL PRIMARY KEY, num INT, data TEXT)")
except:
conn.rollback()
cur.execute("DELETE FROM test")
cur.execute("SELECT setval('test_id_seq', 1, False)")
conn.commit()
"""

View File

@ -3,6 +3,11 @@ The ``connection`` class
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com> .. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
.. testsetup::
from pprint import pprint
import psycopg2.extensions
.. class:: connection .. class:: connection
Handles the connection to a PostgreSQL database instance. It encapsulates Handles the connection to a PostgreSQL database instance. It encapsulates
@ -155,12 +160,16 @@ The ``connection`` class
.. attribute:: notices .. attribute:: notices
A list containing all the database messages sent to the client during A list containing all the database messages sent to the client during
the session. :: the session.
.. doctest::
:options: NORMALIZE_WHITESPACE
>>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);") >>> cur.execute("CREATE TABLE foo (id serial PRIMARY KEY);")
>>> 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']
>>> cur.execute("DROP TABLE foo;")
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. 50 messages are kept.

View File

@ -3,6 +3,19 @@ The ``cursor`` class
.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com> .. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
.. testsetup:: *
from StringIO import StringIO
import sys
create_test_table()
# initial data
cur.executemany("INSERT INTO test (num, data) VALUES (%s, %s)",
[(100, "abc'def"), (None, 'dada'), (42, 'bar')])
conn.commit()
.. class:: cursor .. class:: cursor
Allows Python code to execute PostgreSQL command in a database session. Allows Python code to execute PostgreSQL command in a database session.
@ -123,7 +136,7 @@ The ``cursor`` class
Return a query string after arguments binding. The string returned is Return a query string after arguments binding. The string returned is
exactly the one that would be sent to the database running the exactly the one that would be sent to the database running the
:meth:`~cursor.execute` method or similar. :: :meth:`~cursor.execute` method or similar.
>>> 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')"
@ -189,7 +202,7 @@ The ``cursor`` class
:class:`cursor` objects are iterable, so, instead of calling :class:`cursor` objects are iterable, so, instead of calling
explicitly :meth:`~cursor.fetchone` in a loop, the object itself can explicitly :meth:`~cursor.fetchone` in a loop, the object itself can
be used:: be used:
>>> cur.execute("SELECT * FROM test;") >>> cur.execute("SELECT * FROM test;")
>>> for record in cur: >>> for record in cur:
@ -197,17 +210,17 @@ The ``cursor`` class
... ...
(1, 100, "abc'def") (1, 100, "abc'def")
(2, None, 'dada') (2, None, 'dada')
(4, 42, 'bar') (3, 42, 'bar')
.. method:: fetchone() .. method:: fetchone()
Fetch the next row of a query result set, returning a single tuple, Fetch the next row of a query result set, returning a single tuple,
or ``None`` when no more data is available:: or ``None`` when no more data is available:
>>> cur.execute("SELECT * FROM test WHERE id = %s", (4,)) >>> cur.execute("SELECT * FROM test WHERE id = %s", (3,))
>>> cur.fetchone() >>> cur.fetchone()
(4, 42, 'bar') (3, 42, 'bar')
A :exc:`~psycopg2.ProgrammingError` is raised if the previous call A :exc:`~psycopg2.ProgrammingError` is raised if the previous call
to |execute*|_ did not produce any result set or no call was issued to |execute*|_ did not produce any result set or no call was issued
@ -224,13 +237,13 @@ The ``cursor`` class
the number of rows to be fetched. The method should try to fetch as the number of rows to be fetched. The method should try to fetch as
many rows as indicated by the size parameter. If this is not possible many rows as indicated by the size parameter. If this is not possible
due to the specified number of rows not being available, fewer rows due to the specified number of rows not being available, fewer rows
may be returned:: may be returned:
>>> cur.execute("SELECT * FROM test;") >>> cur.execute("SELECT * FROM test;")
>>> cur.fetchmany(2) >>> cur.fetchmany(2)
[(1, 100, "abc'def"), (2, None, 'dada')] [(1, 100, "abc'def"), (2, None, 'dada')]
>>> cur.fetchmany(2) >>> cur.fetchmany(2)
[(4, 42, 'bar')] [(3, 42, 'bar')]
>>> cur.fetchmany(2) >>> cur.fetchmany(2)
[] []
@ -252,7 +265,7 @@ The ``cursor`` class
>>> cur.execute("SELECT * FROM test;") >>> cur.execute("SELECT * FROM test;")
>>> cur.fetchall() >>> cur.fetchall()
[(1, 100, "abc'def"), (2, None, 'dada'), (4, 42, 'bar')] [(1, 100, "abc'def"), (2, None, 'dada'), (3, 42, 'bar')]
A :exc:`~psycopg2.ProgrammingError` is raised if the previous call to A :exc:`~psycopg2.ProgrammingError` is raised if the previous call to
|execute*|_ did not produce any result set or no call was issued yet. |execute*|_ did not produce any result set or no call was issued yet.
@ -278,7 +291,7 @@ The ``cursor`` class
According to the |DBAPI|_, the exception raised for a cursor out According to the |DBAPI|_, the exception raised for a cursor out
of bound should have been :exc:`!IndexError`. The best option is of bound should have been :exc:`!IndexError`. The best option is
probably to catch both exceptions in your code:: probably to catch both exceptions in your code:
try: try:
cur.scroll(1000 * 1000) cur.scroll(1000 * 1000)
@ -359,7 +372,7 @@ The ``cursor`` class
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). ``None`` if no query has been backend (including bound arguments). ``None`` if no 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
@ -373,7 +386,7 @@ The ``cursor`` class
.. attribute:: statusmessage .. attribute:: statusmessage
Read-only attribute containing the message returned by the last Read-only attribute containing the message returned by the last
command:: command:
>>> 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.statusmessage >>> cur.statusmessage
@ -429,14 +442,13 @@ The ``cursor`` class
The :obj:`!columns` argument is a sequence containing the name of the The :obj:`!columns` argument is a sequence containing the name of the
fields where the read data will be entered. Its length and column fields where the read data will be entered. Its length and column
type should match the content of the read file. If not specifies, it type should match the content of the read file. If not specifies, it
is assumed that the entire table matches the file structure. :: is assumed that the entire table matches the file structure.
>>> f = StringIO("42\tfoo\n74\tbar\n") >>> f = StringIO("42\tfoo\n74\tbar\n")
>>> cur.copy_from(f, 'test', columns=('num', 'data')) >>> cur.copy_from(f, 'test', columns=('num', 'data'))
>>> cur.execute("select * from test where id > 5;") >>> cur.execute("select * from test where id > 5;")
>>> cur.fetchall() >>> cur.fetchall()
[(7, 42, 'foo'), (8, 74, 'bar')] [(6, 42, 'foo'), (7, 74, 'bar')]
.. versionchanged:: 2.0.6 .. versionchanged:: 2.0.6
added the :obj:`columns` parameter. added the :obj:`columns` parameter.
@ -452,11 +464,12 @@ The ``cursor`` class
:obj:`!null` represents :sql:`NULL` values in the file. :obj:`!null` represents :sql:`NULL` values in the file.
The :obj:`!columns` argument is a sequence of field names: if not The :obj:`!columns` argument is a sequence of field names: if not
``None`` only the specified fields will be included in the dump. :: ``None`` only the specified fields will be included in the dump.
>>> cur.copy_to(sys.stdout, 'test', sep="|") >>> cur.copy_to(sys.stdout, 'test', sep="|")
1|100|abc'def 1|100|abc'def
2|\N|dada 2|\N|dada
...
.. versionchanged:: 2.0.6 .. versionchanged:: 2.0.6
added the :obj:`columns` parameter. added the :obj:`columns` parameter.
@ -472,12 +485,13 @@ The ``cursor`` class
open, writeable file for :sql:`COPY TO`. The optional :obj:`!size` open, writeable file for :sql:`COPY TO`. The optional :obj:`!size`
argument, when specified for a :sql:`COPY FROM` statement, will be argument, when specified for a :sql:`COPY FROM` statement, will be
passed to :obj:`!file`\ 's read method to control the read buffer passed to :obj:`!file`\ 's read method to control the read buffer
size. :: size.
>>> cur.copy_expert("COPY test TO STDOUT WITH CSV HEADER", sys.stdout) >>> cur.copy_expert("COPY test TO STDOUT WITH CSV HEADER", sys.stdout)
id,num,data id,num,data
1,100,abc'def 1,100,abc'def
2,,dada 2,,dada
...
.. |COPY| replace:: :sql:`COPY` .. |COPY| replace:: :sql:`COPY`
.. __: http://www.postgresql.org/docs/8.4/static/sql-copy.html .. __: http://www.postgresql.org/docs/8.4/static/sql-copy.html

View File

@ -5,6 +5,11 @@
.. module:: psycopg2.extensions .. module:: psycopg2.extensions
.. testsetup:: *
from psycopg2.extensions import AsIs, QuotedString, ISOLATION_LEVEL_AUTOCOMMIT
from psycopg2._psycopg import Binary
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|_.
@ -94,7 +99,7 @@ deal with Python objects adaptation:
.. method:: getquoted() .. method:: getquoted()
Return the :meth:`str` conversion of the wrapped object. :: Return the :meth:`str` conversion of the wrapped object.
>>> AsIs(42).getquoted() >>> AsIs(42).getquoted()
'42' '42'

View File

@ -5,6 +5,12 @@
.. module:: psycopg2.extras .. module:: psycopg2.extras
.. testsetup::
import psycopg2.extras
create_test_table()
This module is a generic place used to hold little helper functions and This module is a generic place used to hold little helper functions and
classes until a better place in the distribution is found. classes until a better place in the distribution is found.
@ -22,11 +28,13 @@ similar to the Python dictionaries instead of the tuples. You can use it
either passing :class:`DictConnection` as :obj:`!connection_factory` argument either passing :class:`DictConnection` as :obj:`!connection_factory` argument
to the :func:`~psycopg2.connect` function or passing :class:`DictCursor` as to the :func:`~psycopg2.connect` function or passing :class:`DictCursor` as
the :class:`!cursor_factory` argument to the :meth:`~connection.cursor` method the :class:`!cursor_factory` argument to the :meth:`~connection.cursor` method
of a regular :class:`connection`. :: of a regular :class:`connection`.
>>> conn = psycopg2.connect(database='test') >>> dict_cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
>>> cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) >>> dict_cur.execute("INSERT INTO test (num, data) VALUES(%s, %s)",
>>> cur.execute("SELECT * FROM test") ... (100, "abc'def"))
>>> dict_cur.execute("SELECT * FROM test")
>>> rec = dict_cur.fetchone()
>>> rec['id'] >>> rec['id']
1 1
>>> rec['num'] >>> rec['num']
@ -85,17 +93,15 @@ UUID data type
.. versionadded:: 2.0.9 .. versionadded:: 2.0.9
.. versionchanged:: 2.0.13 added UUID array support. .. versionchanged:: 2.0.13 added UUID array support.
::
>>> psycopg2.extras.register_uuid() >>> psycopg2.extras.register_uuid()
<psycopg2._psycopg.type object at 0x...>
# Python UUID can be used in SQL queries >>>
>>> # Python UUID can be used in SQL queries
>>> import uuid
>>> psycopg2.extensions.adapt(uuid.uuid4()).getquoted() >>> psycopg2.extensions.adapt(uuid.uuid4()).getquoted()
"'1ad2b180-c17e-46ad-ad94-e1f0dfc7c34b'::uuid" "'...-...-...-...-...'::uuid"
>>>
# PostgreSQL UUID are transformed into Python UUID objects. >>> # PostgreSQL UUID are transformed into Python UUID objects.
>>> conn = psycopg2.connect(database='test')
>>> cur = conn.cursor()
>>> cur.execute("SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid") >>> cur.execute("SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid")
>>> cur.fetchone()[0] >>> cur.fetchone()[0]
UUID('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11') UUID('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11')
@ -112,12 +118,9 @@ UUID data type
INET data type INET data type
-------------- --------------
::
>>> psycopg2.extras.register_inet() >>> psycopg2.extras.register_inet()
<psycopg2._psycopg.type object at 0x...>
>>> conn = psycopg2.connect(database='test')
>>> cur = conn.cursor()
>>> cur.execute("SELECT '192.168.0.1'::inet") >>> cur.execute("SELECT '192.168.0.1'::inet")
>>> cur.fetchone()[0].addr >>> cur.fetchone()[0].addr
'192.168.0.1' '192.168.0.1'

View File

@ -107,15 +107,18 @@ available through the following exceptions:
.. extension:: .. extension::
The :attr:`~Error.pgerror` and :attr:`~Error.pgcode` attributes are The :attr:`~Error.pgerror` and :attr:`~Error.pgcode` attributes are
Psycopg extensions. :: Psycopg extensions.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> try: >>> try:
... cur.execute("SELECT * FROM barf") ... cur.execute("SELECT * FROM barf")
>>> except Exception, 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

View File

@ -15,7 +15,7 @@ basic commands::
# Connect to an existing database # Connect to an existing database
>>> conn = psycopg2.connect("dbname=test user=postgres") >>> conn = psycopg2.connect("dbname=test user=postgres")
# Open a curstor to perform database operations # Open a cursor to perform database operations
>>> cur = conn.cursor() >>> cur = conn.cursor()
# Execute a command: this creates a new table # Execute a command: this creates a new table