Merge remote-tracking branch 'origin/pycurl' into planetlab-4_0-branch
[plcapi.git] / psycopg2 / examples / dialtone.py
1 """
2 This example/recipe has been contributed by Valentino Volonghi (dialtone)
3
4 Mapping arbitrary objects to a PostgreSQL database with psycopg2
5
6 - Problem
7
8 You need to store arbitrary objects in a PostgreSQL database without being
9 intrusive for your classes (don't want inheritance from an 'Item' or 
10 'Persistent' object).
11
12 - Solution
13 """
14
15 from datetime import datetime
16  
17 import psycopg2
18 from psycopg2.extensions import adapt, register_adapter
19
20 try:
21     sorted()
22 except:
23     def sorted(seq):
24         seq.sort()
25         return seq
26
27 # Here is the adapter for every object that we may ever need to 
28 # insert in the database. It receives the original object and does
29 # its job on that instance
30
31 class ObjectMapper(object):
32     def __init__(self, orig, curs=None):
33         self.orig = orig
34         self.tmp = {}
35         self.items, self.fields = self._gatherState()
36  
37     def _gatherState(self):
38         adaptee_name = self.orig.__class__.__name__
39         fields = sorted([(field, getattr(self.orig, field))
40                         for field in persistent_fields[adaptee_name]])
41         items = []
42         for item, value in fields:
43             items.append(item)
44         return items, fields
45  
46     def getTableName(self):
47         return self.orig.__class__.__name__
48  
49     def getMappedValues(self):
50         tmp = []
51         for i in self.items:
52             tmp.append("%%(%s)s"%i)
53         return ", ".join(tmp)
54  
55     def getValuesDict(self):
56         return dict(self.fields)
57  
58     def getFields(self):
59         return self.items
60
61     def generateInsert(self):
62         qry = "INSERT INTO"
63         qry += " " + self.getTableName() + " ("
64         qry += ", ".join(self.getFields()) + ") VALUES ("
65         qry += self.getMappedValues() + ")"
66         return qry, self.getValuesDict()
67
68 # Here are the objects
69 class Album(object):    
70     id = 0 
71     def __init__(self):
72         self.creation_time = datetime.now()
73         self.album_id = self.id
74         Album.id = Album.id + 1
75         self.binary_data = buffer('12312312312121')
76  
77 class Order(object):
78      id = 0
79      def __init__(self):
80         self.items = ['rice','chocolate']
81         self.price = 34
82         self.order_id = self.id
83         Order.id = Order.id + 1
84
85 register_adapter(Album, ObjectMapper)
86 register_adapter(Order, ObjectMapper)
87     
88 # Describe what is needed to save on each object
89 # This is actually just configuration, you can use xml with a parser if you
90 # like to have plenty of wasted CPU cycles ;P.
91
92 persistent_fields = {'Album': ['album_id', 'creation_time', 'binary_data'],
93                               'Order':  ['order_id', 'items', 'price']
94                             }
95  
96 print adapt(Album()).generateInsert()
97 print adapt(Album()).generateInsert()
98 print adapt(Album()).generateInsert()
99 print adapt(Order()).generateInsert()
100 print adapt(Order()).generateInsert()
101 print adapt(Order()).generateInsert()
102
103 """
104 - Discussion
105
106 Psycopg 2 has a great new feature: adaptation. The big thing about 
107 adaptation is that it enable the programmer to glue most of the 
108 code out there without many difficulties.
109
110 This recipe tries to focus the attention on a way to generate SQL queries to 
111 insert  completely new objects inside a database. As you can see objects do 
112 not know anything about the code that is handling them. We specify all the 
113 fields that we need for each object through the persistent_fields dict.
114
115 The most important lines of this recipe are:
116     register_adapter(Album, ObjectMapper)
117     register_adapter(Order, ObjectMapper)
118
119 In these line we notify the system that when we call adapt with an Album instance 
120 as an argument we want it to istantiate ObjectMapper passing the Album instance  
121 as argument (self.orig in the ObjectMapper class).
122
123 The output is something like this (for each call to generateInsert):
124     
125 ('INSERT INTO Album (album_id, binary_data, creation_time) VALUES 
126    (%(album_id)s, %(binary_data)s, %(creation_time)s)', 
127       
128   {'binary_data': <read-only buffer for 0x402de070, ...>, 
129     'creation_time':   datetime.datetime(2004, 9, 10, 20, 48, 29, 633728), 
130     'album_id': 1}
131 )
132
133 This is a tuple of {SQL_QUERY, FILLING_DICT}, and all the quoting/converting 
134 stuff (from python's datetime to postgres s and from python's buffer to 
135 postgres' blob) is handled with the same adaptation process hunder the hood 
136 by psycopg2.
137
138 At last, just notice that ObjectMapper is working for both Album and Order 
139 instances without any glitches at all, and both classes could have easily been 
140 coming from closed source libraries or C coded ones (which are not easily 
141 modified), whereas a common pattern in todays ORMs or OODBs is to provide 
142 a basic 'Persistent' object that already knows how to store itself in the 
143 database.
144 """