~ubuntu-branches/ubuntu/hardy/psycopg2/hardy

« back to all changes in this revision

Viewing changes to examples/dialtone.py

  • Committer: Bazaar Package Importer
  • Author(s): Fabio Tranchitella
  • Date: 2006-08-09 10:28:30 UTC
  • Revision ID: james.westby@ubuntu.com-20060809102830-grac1dsp24uyqfp4
Tags: upstream-2.0.4
ImportĀ upstreamĀ versionĀ 2.0.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
"""