summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/binary.py88
-rw-r--r--examples/cursor.py64
-rw-r--r--examples/dialtone.py145
-rw-r--r--examples/dt.py91
-rw-r--r--examples/encoding.py102
-rw-r--r--examples/lastrowid.py59
-rw-r--r--examples/mogrify.py47
-rw-r--r--examples/myfirstrecipe.py117
-rw-r--r--examples/simple.py52
-rw-r--r--examples/somehackers.jpgbin0 -> 22565 bytes
-rw-r--r--examples/threads.py160
-rw-r--r--examples/tz.py62
-rw-r--r--examples/whereareyou.jpgbin0 -> 34980 bytes
13 files changed, 987 insertions, 0 deletions
diff --git a/examples/binary.py b/examples/binary.py
new file mode 100644
index 0000000..3b543e5
--- /dev/null
+++ b/examples/binary.py
@@ -0,0 +1,88 @@
+# binary.py - working with binary data
+#
+# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below tis line (except for experimenting)
+
+import sys, psycopg
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+print "Encoding for this connection is", conn.encoding
+
+curs = conn.cursor()
+try:
+ curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)")
+except:
+ conn.rollback()
+ curs.execute("DROP TABLE test_binary")
+ curs.execute("CREATE TABLE test_binary (id int4, name text, img bytea)")
+conn.commit()
+
+# first we try two inserts, one with an explicit Binary call and the other
+# using a buffer on a file object.
+
+data1 = {'id':1, 'name':'somehackers.jpg',
+ 'img':psycopg.Binary(open('somehackers.jpg').read())}
+data2 = {'id':2, 'name':'whereareyou.jpg',
+ 'img':buffer(open('whereareyou.jpg').read())}
+
+curs.execute("""INSERT INTO test_binary
+ VALUES (%(id)d, %(name)s, %(img)s)""", data1)
+curs.execute("""INSERT INTO test_binary
+ VALUES (%(id)d, %(name)s, %(img)s)""", data2)
+
+# now we try to extract the images as simple text strings
+
+print "Extracting the images as strings..."
+curs.execute("SELECT * FROM test_binary")
+
+for row in curs.fetchall():
+ name, ext = row[1].split('.')
+ new_name = name + '_S.' + ext
+ print " writing %s to %s ..." % (name+'.'+ext, new_name),
+ open(new_name, 'wb').write(row[2])
+ print "done"
+ print " python type of image data is", type(row[2])
+
+# extract exactly the same data but using a binary cursor
+
+print "Extracting the images using a binary cursor:"
+
+curs.execute("""DECLARE zot CURSOR FOR
+ SELECT img, name FROM test_binary FOR READ ONLY""")
+curs.execute("""FETCH ALL FROM zot""")
+
+for row in curs.fetchall():
+ name, ext = row[1].split('.')
+ new_name = name + '_B.' + ext
+ print " writing %s to %s ..." % (name+'.'+ext, new_name),
+ open(new_name, 'wb').write(row[0])
+ print "done"
+ print " python type of image data is", type(row[0])
+
+# this rollback is requires because we can't drop a table with a binary cusor
+# declared and still open
+conn.rollback()
+
+curs.execute("DROP TABLE test_binary")
+conn.commit()
+
+print "\nNow try to load the new images, to check it worked!"
diff --git a/examples/cursor.py b/examples/cursor.py
new file mode 100644
index 0000000..2205278
--- /dev/null
+++ b/examples/cursor.py
@@ -0,0 +1,64 @@
+# cursor.py - how to subclass the cursor type
+#
+# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below this line (except for experimenting)
+
+import sys
+import psycopg
+import psycopg.extensions
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dsn:", DSN
+
+conn = psycopg.connect(DSN)
+print "Encoding for this connection is", conn.encoding
+
+
+class NoDataError(psycopg.ProgrammingError):
+ """Exception that will be raised by our cursor."""
+ pass
+
+class Cursor(psycopg.extensions.cursor):
+ """A custom cursor."""
+
+ def fetchone(self):
+ """Like fetchone but raise an exception if no data is available.
+
+ Note that to have .fetchmany() and .fetchall() to raise the same
+ exception we'll have to override them too; even if internally psycopg
+ uses the same function to fetch rows, the code path from Python is
+ different.
+ """
+ d = psycopg.extensions.cursor.fetchone(self)
+ if d is None:
+ raise NoDataError("no more data")
+ return d
+
+curs = conn.cursor(factory=Cursor)
+curs.execute("SELECT 1 AS foo")
+print "Result of fetchone():", curs.fetchone()
+
+# now let's raise the exception
+try:
+ curs.fetchone()
+except NoDataError, err:
+ print "Exception caugth:", err
+
+conn.rollback()
diff --git a/examples/dialtone.py b/examples/dialtone.py
new file mode 100644
index 0000000..9e453c3
--- /dev/null
+++ b/examples/dialtone.py
@@ -0,0 +1,145 @@
+"""
+This example/recipe has been contributed by Valentino Volonghi (dialtone)
+
+Mapping arbitrary objects to a PostgreSQL database with psycopg2
+
+- Problem
+
+You need to store arbitrary objects in a PostgreSQL database without being
+intrusive for your classes (don't want inheritance from an 'Item' or
+'Persistent' object).
+
+- Solution
+"""
+
+from datetime import datetime
+
+import psycopg
+from psycopg.extensions import adapters, adapt
+
+try: sorted()
+except NameError:
+ def sorted(seq):
+ seq.sort()
+ return seq
+
+# Here is the adapter for every object that we may ever need to
+# insert in the database. It receives the original object and does
+# its job on that instance
+
+class ObjectMapper(object):
+ def __init__(self, orig):
+ self.orig = orig
+ self.tmp = {}
+ self.items, self.fields = self._gatherState()
+
+ def _gatherState(self):
+ adaptee_name = self.orig.__class__.__name__
+ fields = sorted([(field, getattr(self.orig, field))
+ for field in persistent_fields[adaptee_name]])
+ items = []
+ for item, value in fields:
+ items.append(item)
+ return items, fields
+
+ def getTableName(self):
+ return self.orig.__class__.__name__
+
+ def getMappedValues(self):
+ tmp = []
+ for i in self.items:
+ tmp.append("%%(%s)s"%i)
+ return ", ".join(tmp)
+
+ def getValuesDict(self):
+ return dict(self.fields)
+
+ def getFields(self):
+ return self.items
+
+ def generateInsert(self):
+ qry = "INSERT INTO"
+ qry += " " + self.getTableName() + " ("
+ qry += ", ".join(self.getFields()) + ") VALUES ("
+ qry += self.getMappedValues() + ")"
+ return qry, self.getValuesDict()
+
+# Here are the objects
+class Album(object):
+ id = 0
+ def __init__(self):
+ self.creation_time = datetime.now()
+ self.album_id = self.id
+ Album.id = Album.id + 1
+ self.binary_data = buffer('12312312312121')
+
+class Order(object):
+ id = 0
+ def __init__(self):
+ self.items = ['rice','chocolate']
+ self.price = 34
+ self.order_id = self.id
+ Order.id = Order.id + 1
+
+adapters.update({Album: ObjectMapper, Order: ObjectMapper})
+
+# Describe what is needed to save on each object
+# This is actually just configuration, you can use xml with a parser if you
+# like to have plenty of wasted CPU cycles ;P.
+
+persistent_fields = {'Album': ['album_id', 'creation_time', 'binary_data'],
+ 'Order': ['order_id', 'items', 'price']
+ }
+
+print adapt(Album()).generateInsert()
+print adapt(Album()).generateInsert()
+print adapt(Album()).generateInsert()
+print adapt(Order()).generateInsert()
+print adapt(Order()).generateInsert()
+print adapt(Order()).generateInsert()
+
+"""
+- Discussion
+
+Psycopg 2 has a great new feature: adaptation. The big thing about
+adaptation is that it enable the programmer to glue most of the
+code out there without many difficulties.
+
+This recipe tries to focus the attention on a way to generate SQL queries to
+insert completely new objects inside a database. As you can see objects do
+not know anything about the code that is handling them. We specify all the
+fields that we need for each object through the persistent_fields dict.
+
+The most important line of this recipe is this one:
+ adapters.update({Album: ObjectMapper, Order: ObjectMapper})
+
+In this line we notify the system that when we call adapt with an Album instance
+as an argument we want it to istantiate ObjectMapper passing the Album instance
+as argument (self.orig in the ObjectMapper class).
+
+adapters is just a python dict with a Key that represents the type
+we need to adapt from and a value that is the adapter
+which will adapt to the wanted interface.
+
+The output is something like this (for each call to generateInsert):
+
+('INSERT INTO Album (album_id, binary_data, creation_time) VALUES
+ (%(album_id)s, %(binary_data)s, %(creation_time)s)',
+
+ {'binary_data': <read-only buffer for 0x402de070, ...>,
+ 'creation_time': datetime.datetime(2004, 9, 10, 20, 48, 29, 633728),
+ 'album_id': 1}
+)
+
+This is a tuple of {SQL_QUERY, FILLING_DICT}, and all the quoting/converting
+stuff (from python's datetime to postgres s and from python's buffer to
+postgres' blob) is handled with the same adaptation process hunder the hood
+by psycopg2.
+
+At last, just notice that ObjectMapper is working for both Album and Order
+instances without any glitches at all, and both classes could have easily been
+coming from closed source libraries or C coded ones (which are not easily
+modified), whereas a common pattern in todays ORMs or OODBs is to provide
+a basic 'Persistent' object that already knows how to store itself in the
+database.
+"""
diff --git a/examples/dt.py b/examples/dt.py
new file mode 100644
index 0000000..f285c7a
--- /dev/null
+++ b/examples/dt.py
@@ -0,0 +1,91 @@
+# datetime.py - example of using date and time types
+#
+# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below tis line (except for experimenting)
+
+import sys, psycopg
+import mx.DateTime
+import datetime
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+curs = conn.cursor()
+
+try:
+ curs.execute("""CREATE TABLE test_dt (k int4, d date, t time, dt timestamp,
+ z interval)""")
+except:
+ conn.rollback()
+ curs.execute("DROP TABLE test_dt")
+ curs.execute("""CREATE TABLE test_dt (k int4,
+ d date, t time, dt timestamp,
+ z interval)""")
+conn.commit()
+
+# build and insert some data using mx.DateTime
+mx1 = (
+ 1,
+ mx.DateTime.Date(2004, 10, 19),
+ mx.DateTime.Time(0, 11, 17.015),
+ mx.DateTime.Timestamp(2004, 10, 19, 0, 11, 17.5),
+ mx.DateTime.DateTimeDelta(13, 15, 17, 59.9))
+
+print "Inserting mx.DateTime values..."
+curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", mx1)
+
+# build and insert some values using the datetime adapters
+dt1 = (
+ 2,
+ datetime.date(2004, 10, 19),
+ datetime.time(0, 11, 17, 15000),
+ datetime.datetime(2004, 10, 19, 0, 11, 17, 500000),
+ datetime.timedelta(13, 15*3600+17*60+59, 900000))
+
+print "Inserting Python datetime values..."
+curs.execute("INSERT INTO test_dt VALUES (%s, %s, %s, %s, %s)", dt1)
+
+# now extract the row from database and print them
+print "Extracting values inserted with mx.DateTime wrappers:"
+curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 1")
+for n, x in zip(mx1[1:], curs.fetchone()):
+ try:
+ # this will work only is psycopg has been compiled with datetime
+ # as the default typecaster for date/time values
+ s = repr(n) + "\n -> " + repr(x) + "\n -> " + x.isoformat()
+ except:
+ s = repr(n) + "\n -> " + repr(x) + "\n -> " + str(x)
+ print s
+print
+
+print "Extracting values inserted with Python datetime wrappers:"
+curs.execute("SELECT d, t, dt, z FROM test_dt WHERE k = 2")
+for n, x in zip(dt1[1:], curs.fetchone()):
+ try:
+ # this will work only is psycopg has been compiled with datetime
+ # as the default typecaster for date/time values
+ s = repr(n) + "\n -> " + repr(x) + "\n -> " + x.isoformat()
+ except:
+ s = repr(n) + "\n -> " + repr(x) + "\n -> " + str(x)
+ print s
+print
+
+curs.execute("DROP TABLE test_dt")
+conn.commit()
diff --git a/examples/encoding.py b/examples/encoding.py
new file mode 100644
index 0000000..1506252
--- /dev/null
+++ b/examples/encoding.py
@@ -0,0 +1,102 @@
+# encoding.py - how to change client encoding (and test it works)
+# -*- encoding: latin-1 -*-
+#
+# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below this line (except for experimenting)
+
+import sys, psycopg
+import psycopg.extensions
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+print "Initial encoding for this connection is", conn.encoding
+
+print "\n** This example is supposed to be run in a UNICODE terminal! **\n"
+
+print "Available encodings:"
+for a, b in psycopg.extensions.encodings.items():
+ print " ", a, "<->", b
+
+print "Using STRING typecaster"
+print "Setting backend encoding to LATIN1 and executing queries:"
+conn.set_client_encoding('LATIN1')
+curs = conn.cursor()
+curs.execute("SELECT %s::TEXT AS foo", ('אטלעש',))
+x = curs.fetchone()[0]
+print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x)
+curs.execute("SELECT %s::TEXT AS foo", (u'אטלעש',))
+x = curs.fetchone()[0]
+print " ->", unicode(x, 'latin-1').encode('utf-8'), type(x)
+
+print "Setting backend encoding to UTF8 and executing queries:"
+conn.set_client_encoding('UNICODE')
+curs = conn.cursor()
+curs.execute("SELECT %s::TEXT AS foo", (u'אטלעש'.encode('utf-8'),))
+x = curs.fetchone()[0]
+print " ->", x, type(x)
+curs.execute("SELECT %s::TEXT AS foo", (u'אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x, type(x)
+
+print "Using UNICODE typecaster"
+psycopg.extensions.register_type(psycopg.extensions.UNICODE)
+
+print "Setting backend encoding to LATIN1 and executing queries:"
+conn.set_client_encoding('LATIN1')
+curs = conn.cursor()
+curs.execute("SELECT %s::TEXT AS foo", ('אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+curs.execute("SELECT %s::TEXT AS foo", (u'אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+
+print "Setting backend encoding to UTF8 and executing queries:"
+conn.set_client_encoding('UNICODE')
+curs = conn.cursor()
+curs.execute("SELECT %s::TEXT AS foo", (u'אטלעש'.encode('utf-8'),))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+curs.execute("SELECT %s::TEXT AS foo", (u'אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+
+print "Executing full UNICODE queries"
+
+print "Setting backend encoding to LATIN1 and executing queries:"
+conn.set_client_encoding('LATIN1')
+curs = conn.cursor()
+curs.execute(u"SELECT %s::TEXT AS foo", ('אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+curs.execute(u"SELECT %s::TEXT AS foo", (u'אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+
+print "Setting backend encoding to UTF8 and executing queries:"
+conn.set_client_encoding('UNICODE')
+curs = conn.cursor()
+curs.execute(u"SELECT %s::TEXT AS foo", (u'אטלעש'.encode('utf-8'),))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
+curs.execute(u"SELECT %s::TEXT AS foo", (u'אטלעש',))
+x = curs.fetchone()[0]
+print " ->", x.encode('utf-8'), ":", type(x)
diff --git a/examples/lastrowid.py b/examples/lastrowid.py
new file mode 100644
index 0000000..aa57850
--- /dev/null
+++ b/examples/lastrowid.py
@@ -0,0 +1,59 @@
+# lastrowid.py - example of using .lastrowid attribute
+#
+# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below tis line (except for experimenting)
+
+import sys, psycopg
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+curs = conn.cursor()
+
+try:
+ curs.execute("CREATE TABLE test_oid (name text, surname text)")
+except:
+ conn.rollback()
+ curs.execute("DROP TABLE test_oid")
+ curs.execute("CREATE TABLE test_oid (name text, surname text)")
+conn.commit()
+
+data = ({'name':'Federico', 'surname':'Di Gregorio'},
+ {'name':'Pierluigi', 'surname':'Di Nunzio'})
+
+curs.execute("""INSERT INTO test_oid
+ VALUES (%(name)s, %(surname)s)""", data[0])
+
+foid = curs.lastrowid
+print "Oid for %(name)s %(surname)s" % data[0], "is", foid
+
+curs.execute("""INSERT INTO test_oid
+ VALUES (%(name)s, %(surname)s)""", data[1])
+moid = curs.lastrowid
+print "Oid for %(name)s %(surname)s" % data[1], "is", moid
+
+curs.execute("SELECT * FROM test_oid WHERE oid = %d", (foid,))
+print "Oid", foid, "selected %s %s" % curs.fetchone()
+
+curs.execute("SELECT * FROM test_oid WHERE oid = %d", (moid,))
+print "Oid", moid, "selected %s %s" % curs.fetchone()
+
+curs.execute("DROP TABLE test_oid")
+conn.commit()
diff --git a/examples/mogrify.py b/examples/mogrify.py
new file mode 100644
index 0000000..ad6d11c
--- /dev/null
+++ b/examples/mogrify.py
@@ -0,0 +1,47 @@
+# mogrify.py - test all possible type mogrifications
+# -*- encoding: latin1 -*-
+#
+# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below this line (except for experimenting)
+
+import sys, psycopg
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+
+conn = psycopg.connect(DSN)
+print "Encoding for this connection is", conn.encoding
+
+curs = conn.cursor()
+curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'})
+curs.execute("SELECT %(foo)s AS foo", {'foo':None})
+curs.execute("SELECT %(foo)s AS foo", {'foo':True})
+curs.execute("SELECT %(foo)f AS foo", {'foo':42})
+curs.execute("SELECT %(foo)s AS foo", {'foo':u'yattא!'})
+curs.execute("SELECT %(foo)s AS foo", {'foo':u'bar'})
+
+print curs.mogrify("SELECT %(foo)s AS foo", {'foo':'bar'})
+print curs.mogrify("SELECT %(foo)s AS foo", {'foo':None})
+print curs.mogrify("SELECT %(foo)s AS foo", {'foo':True})
+print curs.mogrify("SELECT %(foo)f AS foo", {'foo':42})
+print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'yattא!'})
+print curs.mogrify("SELECT %(foo)s AS foo", {'foo':u'bar'})
+
+conn.rollback()
diff --git a/examples/myfirstrecipe.py b/examples/myfirstrecipe.py
new file mode 100644
index 0000000..8457c67
--- /dev/null
+++ b/examples/myfirstrecipe.py
@@ -0,0 +1,117 @@
+"""
+Using a tuple as a bound variable in "SELECT ... IN (...)" clauses
+in PostgreSQL using psycopg 2
+
+Some time ago someone asked on the psycopg mailing list how to have a
+bound variable expand to the right SQL for an SELECT IN clause:
+
+ SELECT * FROM atable WHERE afield IN (value1, value2, value3)
+
+with the values to be used in the IN clause to be passed to the cursor
+.execute() method in a tuple as a bound variable, i.e.:
+
+ in_values = ("value1", "value2", "value3")
+ curs.execute("SELECT ... IN %s", (in_values,))
+
+psycopg 1 does support typecasting from Python to PostgreSQL (and back)
+only for simple types and this problem has no elegant solution (short or
+writing a wrapper class returning the pre-quoted text in an __str__
+method.
+
+But psycopg 2 offers a simple and elegant solution by partially
+implementing the Object Adaptation from PEP 246. psycopg 2 (still in
+beta and currently labeled as 1.99.9) moves the type-casting logic into
+external adapters and a somehow broken adapt() function.
+
+While the original adapt() takes 3 arguments, psycopg's one only takes
+1: the bound variable to be adapted. The result is an object supporting
+a not-yet well defined protocol that we can call IPsycopgSQLQuote:
+
+ class IPsycopgSQLQuote:
+
+ def getquoted(self):
+ "Returns a quoted string representing the bound variable."
+
+ def getbinary(self):
+ "Returns a binary quoted string representing the bound variable."
+
+ def getbuffer(self):
+ "Returns the wrapped object itself."
+
+ __str__ = getquoted
+
+Then one of the functions (usually .getquoted()) is called by psycopg at
+the right time to obtain the right, sql-quoted representation for the
+corresponding bound variable.
+
+The nice part is that the default, built-in adapters, derived from
+psycopg 1 tyecasting code can be overridden by the programmer, simply
+replacing them in the psycopg.extensions.adapters dictionary.
+
+Then the solution to the original problem is now obvious: write an
+adapter that adapts tuple objects into the right SQL string, by calling
+recursively adapt() on each element.
+
+Note: psycopg 2 adapter code is still very young and will probably move
+to a more 'standard' (3 arguments) implementation for the adapt()
+function; as long as that does not slow down too much query execution.
+
+Psycopg 2 development can be tracked on the psycopg mailing list:
+
+ http://lists.initd.org/mailman/listinfo/psycopg
+
+and on the psycopg 2 wiki:
+
+ http://wiki.initd.org/Projects/Psycopg2
+
+"""
+
+import psycopg
+import psycopg.extensions
+from psycopg.extensions import adapt as psycoadapt
+
+class AsIs(object):
+ """An adapter that just return the object 'as is'.
+
+ psycopg 1.99.9 has some optimizations that make impossible to call
+ adapt() without adding some basic adapters externally. This limitation
+ will be lifted in a future release.
+ """
+ def __init__(self, obj):
+ self.__obj = obj
+ def getquoted(self):
+ return self.__obj
+
+class SQL_IN(object):
+ """Adapt a tuple to an SQL quotable object."""
+
+ def __init__(self, seq):
+ self._seq = seq
+
+ 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
+
+ qobjs = [str(psycoadapt(o).getquoted()) for o in self._seq]
+
+ return '(' + ', '.join(qobjs) + ')'
+
+ __str__ = getquoted
+
+# add our new adapter class to psycopg list of adapters
+psycopg.extensions.adapters[tuple] = SQL_IN
+psycopg.extensions.adapters[float] = AsIs
+psycopg.extensions.adapters[int] = AsIs
+
+# usually we would call:
+#
+# conn = psycopg.connect("...")
+# curs = conn.cursor()
+# curs.execute("SELECT ...", (("this", "is", "the", "tuple"),))
+#
+# but we have no connection to a database right now, so we just check
+# the SQL_IN class by calling psycopg's adapt() directly:
+
+if __name__ == '__main__':
+ print "Note how the string will be SQL-quoted, but the number will not:"
+ print psycoadapt(("this is an 'sql quoted' str\\ing", 1, 2.0))
diff --git a/examples/simple.py b/examples/simple.py
new file mode 100644
index 0000000..08a3607
--- /dev/null
+++ b/examples/simple.py
@@ -0,0 +1,52 @@
+# simple.py - very simple example of plain DBAPI-2.0 usage
+# currently used as test-me-stress-me script for psycopg 2.0
+#
+# Copyright (C) 2001-2003 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below this line (except for experimenting)
+
+class SimpleQuoter(object):
+ def sqlquote(x=None):
+ return "'bar'"
+
+import sys, psycopg
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+print "Encoding for this connection is", conn.encoding
+
+curs = conn.cursor()
+curs.execute("SELECT 1 AS foo")
+print curs.fetchone()
+curs.execute("SELECT 1 AS foo")
+print curs.fetchmany()
+curs.execute("SELECT 1 AS foo")
+print curs.fetchall()
+
+conn.rollback()
+
+sys.exit(0)
+
+curs.execute("SELECT 1 AS foo", async=1)
+
+curs.execute("SELECT %(foo)s AS foo", {'foo':'bar'})
+curs.execute("SELECT %(foo)s AS foo", {'foo':None})
+curs.execute("SELECT %(foo)f AS foo", {'foo':42})
+curs.execute("SELECT %(foo)s AS foo", {'foo':SimpleQuoter()})
diff --git a/examples/somehackers.jpg b/examples/somehackers.jpg
new file mode 100644
index 0000000..8bb6e01
--- /dev/null
+++ b/examples/somehackers.jpg
Binary files differ
diff --git a/examples/threads.py b/examples/threads.py
new file mode 100644
index 0000000..ca67104
--- /dev/null
+++ b/examples/threads.py
@@ -0,0 +1,160 @@
+# threads.py -- example of multiple threads using psycopg
+# -*- encoding: latin1 -*-
+#
+# Copyright (C) 2001-2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## some others parameters
+INSERT_THREADS = ('A', 'B', 'C')
+SELECT_THREADS = ('1', '2')
+
+ROWS = 1000
+
+COMMIT_STEP = 20
+SELECT_SIZE = 10000
+SELECT_STEP = 500
+SELECT_DIV = 250
+
+# the available modes are:
+# 0 - one connection for all insert and one for all select threads
+# 1 - connections generated using the connection pool
+
+MODE = 1
+
+## don't modify anything below tis line (except for experimenting)
+
+import sys, psycopg, threading
+from psycopg.pool import ThreadedConnectionPool
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+if len(sys.argv) > 2:
+ MODE = int(sys.argv[2])
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+curs = conn.cursor()
+
+try:
+ curs.execute("""CREATE TABLE test_threads (
+ name text, value1 int4, value2 float)""")
+except:
+ conn.rollback()
+ curs.execute("DROP TABLE test_threads")
+ curs.execute("""CREATE TABLE test_threads (
+ name text, value1 int4, value2 float)""")
+conn.commit()
+
+
+## this function inserts a big number of rows and creates and destroys
+## a large number of cursors
+
+def insert_func(conn_or_pool, rows):
+ name = threading.currentThread().getName()
+
+ if MODE == 0:
+ conn = conn_or_pool
+ else:
+ conn = conn_or_pool.getconn()
+
+ for i in range(rows):
+ if divmod(i, COMMIT_STEP)[1] == 0:
+ conn.commit()
+ if MODE == 1:
+ conn_or_pool.putconn(conn)
+ s = name + ": COMMIT STEP " + str(i)
+ print s
+ if MODE == 1:
+ conn = conn_or_pool.getconn()
+ c = conn.cursor()
+ try:
+ c.execute("INSERT INTO test_threads VALUES (%s, %d, %f)",
+ (str(i), i, float(i)))
+ except psycopg.ProgrammingError, err:
+ print name, ": an error occurred; skipping this insert"
+ print err
+ conn.commit()
+
+## a nice select function that prints the current number of rows in the
+## database (and transefer them, putting some pressure on the network)
+
+def select_func(conn_or_pool, z):
+ name = threading.currentThread().getName()
+
+ if MODE == 0:
+ conn = conn_or_pool
+ conn.set_isolation_level(0)
+
+ for i in range(SELECT_SIZE):
+ if divmod(i, SELECT_STEP)[1] == 0:
+ try:
+ if MODE == 1:
+ conn = conn_or_pool.getconn()
+ conn.set_isolation_level(0)
+ c = conn.cursor()
+ c.execute("SELECT * FROM test_threads WHERE value2 < %s",
+ (int(i/z),))
+ l = c.fetchall()
+ if MODE == 1:
+ conn_or_pool.putconn(conn)
+ s = name + ": number of rows fetched: " + str(len(l))
+ print s
+ except psycopg.ProgrammingError, err:
+ print name, ": an error occurred; skipping this select"
+ print err
+
+## create the connection pool or the connections
+if MODE == 0:
+ conn_insert = psycopg.connect(DSN)
+ conn_select = psycopg.connect(DSN)
+else:
+ m = len(INSERT_THREADS) + len(SELECT_THREADS)
+ n = m/2
+ conn_insert = conn_select = ThreadedConnectionPool(n, m, DSN)
+
+## create the threads
+threads = []
+
+print "Creating INSERT threads:"
+for name in INSERT_THREADS:
+ t = threading.Thread(None, insert_func, 'Thread-'+name,
+ (conn_insert, ROWS))
+ t.setDaemon(0)
+ threads.append(t)
+
+print "Creating SELECT threads:"
+for name in SELECT_THREADS:
+ t = threading.Thread(None, select_func, 'Thread-'+name,
+ (conn_select, SELECT_DIV))
+ t.setDaemon(0)
+ threads.append(t)
+
+## really start the threads now
+for t in threads:
+ t.start()
+
+# and wait for them to finish
+for t in threads:
+ t.join()
+ print t.getName(), "exited OK"
+
+
+conn.commit()
+curs.execute("SELECT count(name) FROM test_threads")
+print "Inserted", curs.fetchone()[0], "rows."
+
+curs.execute("DROP TABLE test_threads")
+conn.commit()
diff --git a/examples/tz.py b/examples/tz.py
new file mode 100644
index 0000000..ba760f2
--- /dev/null
+++ b/examples/tz.py
@@ -0,0 +1,62 @@
+# tz.py - example of datetime objects with time zones
+# -*- encoding: latin1 -*-
+#
+# Copyright (C) 2004 Federico Di Gregorio <fog@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+## put in DSN your DSN string
+
+DSN = 'dbname=test'
+
+## don't modify anything below this line (except for experimenting)
+
+import sys, psycopg
+import datetime
+
+from psycopg.tz import ZERO, LOCAL, FixedOffsetTimezone
+
+if len(sys.argv) > 1:
+ DSN = sys.argv[1]
+
+print "Opening connection using dns:", DSN
+conn = psycopg.connect(DSN)
+curs = conn.cursor()
+
+try:
+ curs.execute("CREATE TABLE test_tz (t timestamp with time zone)")
+except:
+ conn.rollback()
+ curs.execute("DROP TABLE test_tz")
+ curs.execute("CREATE TABLE test_tz (t timestamp with time zone)")
+conn.commit()
+
+d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=LOCAL)
+curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
+print "Inserted timestamp with timezone:", d
+print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
+
+tz = FixedOffsetTimezone(-5*60, "EST")
+d = datetime.datetime(1971, 10, 19, 22, 30, 0, tzinfo=tz)
+curs.execute("INSERT INTO test_tz VALUES (%s)", (d,))
+print "Inserted timestamp with timezone:", d
+print "Time zone:", d.tzinfo.tzname(d), "offset:", d.tzinfo.utcoffset(d)
+
+curs.tzinfo_factory = FixedOffsetTimezone
+curs.execute("SELECT * FROM test_tz")
+for d in curs:
+ u = d[0].utcoffset() or ZERO
+ print "UTC time: ", d[0] - u
+ print "Local time:", d[0]
+ print "Time zone:", d[0].tzinfo.tzname(d[0]), d[0].tzinfo.utcoffset(d[0])
+
+curs.execute("DROP TABLE test_tz")
+conn.commit()
diff --git a/examples/whereareyou.jpg b/examples/whereareyou.jpg
new file mode 100644
index 0000000..f508c0b
--- /dev/null
+++ b/examples/whereareyou.jpg
Binary files differ