Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

root/tags/1.4.0/doc/intro.html

Revision 165 (checked in by fumanchu, 6 years ago)

Now distributing recur module with dejavu.

Line 
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2    "http://www.w3.org/TR/xhtml1/DTD/strict.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
5 <head>
6     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7     <title>Dejavu: Introduction</title>
8     <link href='dejavu.css' rel='stylesheet' type='text/css' />
9 </head>
10
11 <body>
12
13 <h2>Introduction</h2>
14
15 <p>Dejavu is an Object-Relational Mapper for Python applications. It is
16 designed to provide the "Model" third of an MVC application. When you build
17 an application using Dejavu, you must supply the Controller(s) and View(s)
18 yourself. Dejavu does not provide these, and does its best to not limit
19 your choices regarding them.</p>
20
21 <p>If you're familiar with Martin Fowler's work <a href='#fowler'>[1]</a>,
22 you can think of Dejavu as providing a Data Source layer, plus the tools
23 to write your own Domain layer. For the Presentation layer, you're on your
24 own. ;)</p>
25
26 <h3>Basic Structure</h3>
27 <p>Developers build their Model by creating classes which subclass
28 the class <tt>dejavu.Unit</tt>; for those of you stuck in RDBMS
29 mindsets, each Unit subclass corresponds to a table; instances of the class
30 correspond to the rows. Each subclass possesses a set of attributes known
31 as "properties", which you can think of as columns in your database
32 table. These attributes are generally formed from a <tt>UnitProperty</tt>
33 descriptor. Any Unit data which needs to be persisted ought to be contained
34 in a Unit Property. However, Unit classes can also possess arbitrary
35 methods and attributes which aid their use within the application.</p>
36
37 <p>Unit classes can be <i>associated</i> to other Unit classes. This means
38 that one of the properties of UnitA maps to one of the properties of UnitB.
39 Related objects may then be looked up more easily.</p>
40
41 <p>Units are managed in memory by <tt>Sandbox</tt> objects, which function
42 as "Identity Maps" <a href='#fowler'>[1]</a>: in-memory caches of Units
43 which keep commit conflicts to a minimum. Unit objects can be "memorized"
44 and "recalled" from a <tt>Sandbox</tt>, using pure Python lambda expressions
45 <a href='#cpython'>[2]</a> as a query language. The lambda is wrapped
46 in an <tt>Expression</tt> object to make it portable.</p>
47
48 <p>Sandboxes persist Unit data by <tt>StorageManager</tt> objects. Each
49 persistence mechanism has its own subclass of the <tt>StorageManager</tt>
50 class; for example, persisting Unit data to a Microsoft SQL Server database
51 requires a <tt>StorageManagerADO_SQLServer</tt> object. When recalling data,
52 Storage Managers receive Expression objects; database SM's, for example,
53 will typically examine these Expressions and produce SQL statements from
54 them, which they then use to retrieve data. Storage Managers also handle
55 the creation of new Units, and their destruction.</p>
56
57 <p>Finally, Dejavu provides a core <tt>Arena</tt> class which you should be
58 able to leverage for any sort of application you are building. The Arena
59 object functions as a top-level "Application" object, collecting the global
60 settings for an application into one place. It doles out Sandboxes,
61 maintains a registry of Units and their associations, and manages startup
62 and shutdown operations.</p>
63
64
65 <h3>Simple Example</h3>
66
67 <p>Since a block of code is often worth a thousand words, here's a minimal
68 example of a Dejavu application:</p>
69
70 zookeeper.py
71 <pre>import dejavu
72
73 class Zoo(dejavu.Unit):
74     Name = dejavu.UnitProperty()
75     Size = dejavu.UnitProperty(int)
76    
77     def total_legs(self):
78         return sum([x.Legs for x in self.Animal()])
79
80 class Animal(dejavu.Unit):
81     Legs = dejavu.UnitProperty(int, default=4)
82
83 Animal.set_properties({"Name": unicode,
84                        "ZooID": int,
85                        })
86 Animal.many_to_one('ZooID', Zoo, 'ID')
87
88 # Set up a global Arena object.
89 arena = dejavu.Arena()
90 conf = {u'Connect':
91         r"PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=C:\zookeeper.mdb;"}
92 arena.add_store("main", "dejavu.storage.storeado.StorageManagerADO_MSAccess", conf))
93 arena.register_all(globals())</pre>
94
95 <p>The above creates the model for the zookeeper application.
96 There are three basic things happening:
97     <ol>
98         <li>The <tt>Zoo</tt> and <tt>Animal</tt> classes, which subclass
99             <tt>dejavu.Unit</tt>. These will correspond to the Zoo and
100             Animal tables within the database. Notice the two different
101             methods of declaring Unit properties. Each class also inherits
102             an 'ID' property (an int) from <tt>dejavu.Unit</tt>.</li>
103         <li>The association between the Animal class and the Zoo class
104             (many-to-one).</li>
105         <li>The setup of a dejavu <tt>Arena</tt> object, including a Storage
106             Manager which uses a Microsoft Access (Jet) database.</li>
107     </ol>
108 </p>
109
110 <p>Here's a simple interactive session which uses the above (assume that
111 tables have been created and populated elsewhere):</p>
112
113 <pre>>>> import zookeeper
114 >>> box = zookeeper.arena.new_sandbox()
115 >>> box.recall(zookeeper.Animal)
116 [&lt;zookeeper.Animal object at 0x013281F0>, &lt;zookeeper.Animal object at 0x01328150>,
117  &lt;zookeeper.Animal object at 0x01328130>, &lt;zookeeper.Animal object at 0x01328230>]
118 >>> box.recall(zookeeper.Zoo)
119 []
120 >>> zoo = zookeeper.Zoo(Name='San Diego Zoo', Size='38')
121 >>> box.memorize(zoo)
122 >>> zoo.ID
123 1
124 >>> box.unit(zookeeper.Zoo, ID=1) is zoo
125 True
126 >>> for creature in box.recall(zookeeper.Animal):
127         zoo.add(creature)
128 >>> len(zoo.Animal())
129 4</pre>
130
131
132 <h3>Design Goals</h3>
133
134 <p>Dejavu is designed to function in environments with complex integration
135 needs, and tends to separate concerns as much as possible. In particular,
136 Dejavu tries to avoid making decisions in the framework which are better
137 left to developers. Some of those decisions are:
138     <ul>
139         <li>Which user interface style to use.</li>
140         <li>Application package architecture. You can place your application
141             within a single Python module, or develop complete packages.</li>
142         <li>Which types to use for <tt>Unit</tt> properties.</li>
143         <li>Which keys to use when associating <tt>Unit</tt> classes.</li>
144     </ul>
145
146 In the same way, Dejavu tries to avoid having developers make decisions
147 which are better left to deployers. Some of those decisions are:
148     <ul>
149         <li>Where (and how) to log error messages.</li>
150         <li>Which storage mechanism (database or ...?) to use. In particular,
151             deployers are allowed to mix and match stores, including how
152             and when to cache objects in memory. Dejavu tries to make
153             it easy for <i>deployers</i> to tune applications to their
154             particular environment.</li>
155     </ul>
156 </p>
157
158 <p>Unlike most generic storage wrappers, Dejavu does not <i>require</i> you
159 to have complete control of your back end. For example, consider Mission
160 Control, the first application built on Dejavu. Mission Control required
161 an ORM which transparently supported two very different backends. Half of
162 the data was to be stored in an MS Access database (which is being migrated
163 to SQL Server), over which the application developers had full control.
164 But half of the data was stored in a third-party application, "The Raiser's
165 Edge" (RE) from Blackbaud. RE provides read-only database access; all writes
166 must go through their object-oriented API. Further, reading via that API was
167 found to be too slow. Therefore, a custom Storage Manager (about 2500 lines
168 of code) was developed, which searches for and loads objects via SQL, but
169 writes Unit data via the REAPI. Dejavu allows the application logic to be
170 completely ignorant of this complex mass of storage details. If Blackbaud
171 closed its doors tomorrow, the solution could be quickly migrated to another
172 data store; business downtime is reduced in the face of inevitable change.<p>
173
174 <h3>Obtaining and Installing</h3>
175
176 <p>You can obtain Dejavu from its Subversion repository at
177 <tt>http://projects.amor.org/dejavu/svn/trunk</tt>. Dejavu is designed to be
178 installed in <tt>site-packages/dejavu</tt> or some other root python
179 path.</p>
180
181 <p>Dejavu was built using Python 2.3.2. You should probably use
182 at least 2.3; Dejavu depends upon the <tt>datetime</tt> module.
183 Although Dejavu <i>supports</i> additional modules like
184 <tt>fixedpoint</tt> and <tt>decimal</tt>, it does not <i>require</i>
185 them.</p>
186
187 <p>Dejavu uses bytecode hacks, and therefore requires CPython
188 <a href='#cpython'>[2]</a>.</p>
189
190 <h3>Compared To Other Database Wrappers</h3>
191 <h4>SQLObject</h4>
192 <p>No matter what project I start on, odds are I'll discover that Ian
193 Bicking has already done the same thing, usually better.
194 <br />See http://blog.ianbicking.org/another-less-sleepy-alternative-to-hibernate.html
195 <br />Which was a reply to Ruby's ActiveRecord:
196 http://www.loudthinking.com/arc/000297.html
197 <br />Which was a reply to Java's Hibernate:
198 http://informit.com/guides/content.asp?g=java&seqNum=127&f1=rss</p>
199
200 <p>Using dejavu, the application developer supplies the following code
201 to define the Units and their relationships:</p>
202
203 <pre>from dejavu import *
204 import fixedpoint   # or decimal, for Python 2.4+
205 import datetime
206
207 class Book(Unit):
208     # The ID field is already set to 'int' for all Unit subclasses.
209     title = UnitProperty(str)
210     price = UnitProperty(fixedpoint.Fixedpoint)
211     publishDate = UnitProperty(datetime.datetime)
212     publisher = UnitProperty(int)
213    
214     def addAuthor(self, author):
215         a = Authorship()
216         a.authorID = author.ID
217         a.bookID = self.ID
218         self.sandbox.memorize(a)
219    
220     def author_names(self):
221         names = []
222         for authorship in self.Authorship():
223             author = authorship.Author()
224             if author:
225                 names.append(author.name)
226         return u', '.join(names)
227
228 class Publisher(Unit):
229     name = UnitProperty(str)
230
231 class Author(Unit):
232     name = UnitProperty(str)
233
234 class Authorship(Unit):
235     authorID = UnitProperty(int)
236     bookID = UnitProperty(int)
237
238 Book.many_to_one('publisher', Publisher, 'ID')
239 Authorship.many_to_one('bookID', Book, 'ID')
240 Authorship.many_to_one('authorID', Author, 'ID')
241
242 arena = Arena()
243 arena.register_all(globals())
244 </pre>
245
246
247 <p>The deployer would write in a .conf file:</p>
248 <pre>[Books]
249 Class: dejavu.storage.storepypgsql.StorageManagerPgSQL
250 Connect: host=localhost dbname=bookstore user=postgres password=****
251 Create If Missing: True</pre>
252
253 <p>To create the tables:</p>
254 <pre>for cls in (Author, Publisher, Book):
255     arena.create_storage(cls)</pre>
256
257 <p>The app developer's runtime code reads as follows:</p>
258 <pre>
259 box = arena.new_sandbox()
260 ppython = Book()
261 ppython.title = 'Programming Python'
262 ppython.price = 20
263 ppython.publishDate = datetime.datetime(2001, 3, 1)
264 # This next line is redundant; all properties default to None.
265 # But explicitness is rarely a bad thing.
266 ppython.publisher = None
267 box.memorize(ppython)
268
269 print ppython.title # output: 'Programming Python'
270
271 mlutz = Author(name = 'Mark Lutz')
272 box.memorize(mlutz) # give mlutz an ID
273 ppython.addAuthor(mlutz)
274
275 print len(ppython.Authorship()) # output: 1
276 print ppython.author_names() # output: 'Mark Lutz'
277
278 oreilly = Publisher(name="O'Reilly")
279 box.memorize(oreilly) # give oreilly an ID
280
281 ppython.publisher = oreilly.ID
282 print ppython.Publisher().name # output: "O'Reilly"
283
284 print len(oreilly.Book()) # output: 1
285
286 print 'Hi,', oreilly.Book().author_names() # output: "Hi, Mark Lutz"
287 </pre>
288 </p>
289
290 <hr />
291
292 <p><a name='fowler'>[1]</a> Fowler,
293 <a href='http://www.martinfowler.com/eaaCatalog/identityMap.html'>Patterns
294 of Enterprise Application Architecture</a>.<br />
295 <a name='cpython'>[2]</a> Dejavu relies upon bytecode hacking to achieve
296 its clean lambda syntax for data queries. Therefore, it is CPython-specific.
297 In addition, the bytecode of Python may change from one version of Python
298 to another; if you find your version of Python does not work with Dejavu's
299 <tt>codewalk</tt> and <tt>logic</tt> modules, please let me know.<br />
300 </p>
301
302 </body>
303 </html>
Note: See TracBrowser for help on using the browser.