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-cursor:
.. testsetup:: *
import re
cur.execute("CREATE TABLE atable (apoint point)")
conn.commit()
Connection and cursor factories
-------------------------------
@ -42,8 +49,8 @@ An example of cursor subclass performing logging is::
raise
conn = psycopg2.connect(DSN)
curs = conn.cursor(cursor_factory=LoggingCursor)
curs.execute("INSERT INTO mytable VALUES (%s, %s, %s);",
cur = conn.cursor(cursor_factory=LoggingCursor)
cur.execute("INSERT INTO mytable VALUES (%s, %s, %s);",
(10, 20, 30))
@ -78,22 +85,24 @@ conversion of the wrapped object.
single: Example; Types adaptation
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):
def __init__(self, x, y):
self.x = x
self.y = y
>>> from psycopg2.extensions import adapt, register_adapter, AsIs
def adapt_point(point):
return AsIs("'(%s, %s)'" % (adapt(point.x), adapt(point.y)))
>>> class Point(object):
... 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)",
(Point(1.23, 4.56),))
>>> register_adapter(Point, adapt_point)
>>> cur.execute("INSERT INTO atable (apoint) VALUES (%s)",
... (Point(1.23, 4.56),))
.. |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
cursor currently being read, and should return a new Python object. For
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):
if value is None:
return None
# Convert from (f1, f2) syntax using a regular expression.
m = re.match(r"\(([^)]+),([^)]+)\)", value)
if m:
return Point(float(m.group(1)), float(m.group(2)))
else:
raise InterfaceError("bad point representation: %r" % value)
>>> def cast_point(value, cur):
... if value is None:
... return None
...
... # Convert from (f1, f2) syntax using a regular expression.
... m = re.match(r"\(([^)]+),([^)]+)\)", value)
... if m:
... return Point(float(m.group(1)), float(m.group(2)))
... else:
... raise InterfaceError("bad point representation: %r" % value)
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
column of the :attr:`cursor.description`::
column of the :attr:`cursor.description`:
curs.execute("SELECT NULL::point")
point_oid = curs.description[0][1] # usually returns 600
>>> cur.execute("SELECT NULL::point")
>>> point_oid = cur.description[0][1] # usually returns 600
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("""
SELECT pg_type.oid
FROM pg_type JOIN pg_namespace
ON typnamespace = pg_namespace.oid
WHERE typname = %(typename)s
AND nspname = %(namespace)s""",
{'typename': 'point', 'namespace': 'pg_catalog'})
>>> cur.execute("""
... SELECT pg_type.oid
... FROM pg_type JOIN pg_namespace
... ON typnamespace = pg_namespace.oid
... WHERE typname = %(typename)s
... AND nspname = %(namespace)s""",
... {'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
(more than one can be specified) to the adapter function.
:func:`~psycopg2.extensions.register_type` completes the spell. Conversion
is automatically performed when a column whose type is a registered OID is
read::
read:
>>> curs.execute("SELECT '(10.2,20.3)'::point")
>>> point = curs.fetchone()[0]
>>> cur.execute("SELECT '(10.2,20.3)'::point")
>>> point = cur.fetchone()[0]
>>> 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
.. 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
# 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.
extensions += [ 'dbapi_extension', 'sql_role' ]
@ -222,3 +223,31 @@ latex_documents = [
# If false, no module index is generated.
#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>
.. testsetup::
from pprint import pprint
import psycopg2.extensions
.. class:: connection
Handles the connection to a PostgreSQL database instance. It encapsulates
@ -155,12 +160,16 @@ The ``connection`` class
.. attribute:: notices
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);")
>>> conn.notices
>>> pprint(conn.notices)
['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']
>>> cur.execute("DROP TABLE foo;")
To avoid a leak in case excessive notices are generated, only the last
50 messages are kept.

View File

@ -3,6 +3,19 @@ The ``cursor`` class
.. 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
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
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'))
"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
explicitly :meth:`~cursor.fetchone` in a loop, the object itself can
be used::
be used:
>>> cur.execute("SELECT * FROM test;")
>>> for record in cur:
@ -197,17 +210,17 @@ The ``cursor`` class
...
(1, 100, "abc'def")
(2, None, 'dada')
(4, 42, 'bar')
(3, 42, 'bar')
.. method:: fetchone()
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()
(4, 42, 'bar')
(3, 42, 'bar')
A :exc:`~psycopg2.ProgrammingError` is raised if the previous call
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
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
may be returned::
may be returned:
>>> cur.execute("SELECT * FROM test;")
>>> cur.fetchmany(2)
[(1, 100, "abc'def"), (2, None, 'dada')]
>>> cur.fetchmany(2)
[(4, 42, 'bar')]
[(3, 42, 'bar')]
>>> cur.fetchmany(2)
[]
@ -252,7 +265,7 @@ The ``cursor`` class
>>> cur.execute("SELECT * FROM test;")
>>> 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
|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
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:
cur.scroll(1000 * 1000)
@ -359,7 +372,7 @@ The ``cursor`` class
Read-only attribute containing the body of the last query sent to the
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.query
@ -373,7 +386,7 @@ The ``cursor`` class
.. attribute:: statusmessage
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.statusmessage
@ -429,14 +442,13 @@ The ``cursor`` class
The :obj:`!columns` argument is a sequence containing the name of the
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
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")
>>> cur.copy_from(f, 'test', columns=('num', 'data'))
>>> cur.execute("select * from test where id > 5;")
>>> cur.fetchall()
[(7, 42, 'foo'), (8, 74, 'bar')]
[(6, 42, 'foo'), (7, 74, 'bar')]
.. versionchanged:: 2.0.6
added the :obj:`columns` parameter.
@ -452,11 +464,12 @@ The ``cursor`` class
:obj:`!null` represents :sql:`NULL` values in the file.
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="|")
1|100|abc'def
2|\N|dada
...
.. versionchanged:: 2.0.6
added the :obj:`columns` parameter.
@ -472,12 +485,13 @@ The ``cursor`` class
open, writeable file for :sql:`COPY TO`. The optional :obj:`!size`
argument, when specified for a :sql:`COPY FROM` statement, will be
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)
id,num,data
1,100,abc'def
2,,dada
...
.. |COPY| replace:: :sql:`COPY`
.. __: http://www.postgresql.org/docs/8.4/static/sql-copy.html

View File

@ -5,6 +5,11 @@
.. 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
functionalities defined by the |DBAPI|_.
@ -94,7 +99,7 @@ deal with Python objects adaptation:
.. method:: getquoted()
Return the :meth:`str` conversion of the wrapped object. ::
Return the :meth:`str` conversion of the wrapped object.
>>> AsIs(42).getquoted()
'42'

View File

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

View File

@ -107,15 +107,18 @@ available through the following exceptions:
.. extension::
The :attr:`~Error.pgerror` and :attr:`~Error.pgcode` attributes are
Psycopg extensions. ::
Psycopg extensions.
.. doctest::
:options: +NORMALIZE_WHITESPACE
>>> try:
... cur.execute("SELECT * FROM barf")
>>> except Exception, e:
.... pass
... except Exception, e:
... pass
>>> e.pgcode
>>> '42P01'
'42P01'
>>> print e.pgerror
ERROR: relation "barf" does not exist
LINE 1: SELECT * FROM barf

View File

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