| 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 a thread-safe Object-Relational Mapper for Python applications. |
|---|
| 16 |
It is designed to provide the "Model" third of an MVC application. When you |
|---|
| 17 |
build an application using Dejavu, you must supply the Controller(s) and |
|---|
| 18 |
View(s) yourself. Dejavu does not provide these, and does its best to not |
|---|
| 19 |
limit 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. ;) It primarily uses a generic Data Mapper architecture (as opposed |
|---|
| 25 |
to the more tightly-coupled Active Record architecture).</p> |
|---|
| 26 |
|
|---|
| 27 |
<h3>Basic Structure</h3> |
|---|
| 28 |
<p>Developers build their Model by creating classes which subclass |
|---|
| 29 |
<tt>dejavu.Unit</tt>; in RDBMS terminology, |
|---|
| 30 |
each Unit subclass corresponds to a table; instances of the class |
|---|
| 31 |
correspond to the rows. Each subclass possesses a set of attributes known |
|---|
| 32 |
as "properties", which you can think of as columns in your database |
|---|
| 33 |
table. These attributes are generally formed from a <tt>UnitProperty</tt> |
|---|
| 34 |
descriptor. Any Unit data which needs to be persisted ought to be contained |
|---|
| 35 |
in a Unit Property. However, Unit classes can also possess arbitrary |
|---|
| 36 |
methods and attributes which aid their use within the application.</p> |
|---|
| 37 |
|
|---|
| 38 |
<p>Unit classes can be <i>associated</i> to other Unit classes. This means |
|---|
| 39 |
that one of the properties of UnitA maps to one of the properties of UnitB. |
|---|
| 40 |
Related objects may then be looked up more easily.</p> |
|---|
| 41 |
|
|---|
| 42 |
<p>Units are managed in memory by <tt>Sandbox</tt> objects, which function |
|---|
| 43 |
as "Identity Maps" <a href='#fowler'>[1]</a>: in-memory caches of Units |
|---|
| 44 |
which keep commit conflicts to a minimum. Unit objects can be "memorized" |
|---|
| 45 |
and "recalled" from a <tt>Sandbox</tt>, using pure Python lambda expressions |
|---|
| 46 |
<a href='#cpython'>[2]</a> as a query language. The lambda is wrapped |
|---|
| 47 |
in an <tt>Expression</tt> object to make it portable.</p> |
|---|
| 48 |
|
|---|
| 49 |
<p>Sandboxes persist Unit data by <tt>StorageManager</tt> objects. Each |
|---|
| 50 |
persistence mechanism has its own subclass of the <tt>StorageManager</tt> |
|---|
| 51 |
class; for example, persisting Unit data to a Microsoft SQL Server database |
|---|
| 52 |
requires a <tt>StorageManagerADO_SQLServer</tt> object. When recalling data, |
|---|
| 53 |
Storage Managers receive Expression objects; database SM's, for example, |
|---|
| 54 |
will typically examine these Expressions and produce SQL statements from |
|---|
| 55 |
them, which they then use to retrieve data. Storage Managers also handle |
|---|
| 56 |
the creation of new Units, and their destruction.</p> |
|---|
| 57 |
|
|---|
| 58 |
<p>Finally, Dejavu provides a core <tt>Arena</tt> class which you should be |
|---|
| 59 |
able to leverage for any sort of application you are building. The Arena |
|---|
| 60 |
object functions as a top-level "Application" object, collecting the global |
|---|
| 61 |
settings for an application into one place. It doles out Sandboxes, |
|---|
| 62 |
maintains a registry of Units and their associations, and manages startup |
|---|
| 63 |
and shutdown operations.</p> |
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 |
<h3>Simple Example</h3> |
|---|
| 67 |
|
|---|
| 68 |
<p>Since a block of code is often worth a thousand words, here's a minimal |
|---|
| 69 |
example of a Dejavu application:</p> |
|---|
| 70 |
|
|---|
| 71 |
zookeeper.py |
|---|
| 72 |
<pre>import dejavu |
|---|
| 73 |
|
|---|
| 74 |
class Zoo(dejavu.Unit): |
|---|
| 75 |
Name = dejavu.UnitProperty() |
|---|
| 76 |
Size = dejavu.UnitProperty(int) |
|---|
| 77 |
|
|---|
| 78 |
def total_legs(self): |
|---|
| 79 |
return sum([x.Legs for x in self.Animal()]) |
|---|
| 80 |
|
|---|
| 81 |
class Animal(dejavu.Unit): |
|---|
| 82 |
Legs = dejavu.UnitProperty(int, default=4) |
|---|
| 83 |
|
|---|
| 84 |
Animal.set_properties({"Name": unicode, |
|---|
| 85 |
"ZooID": int, |
|---|
| 86 |
}) |
|---|
| 87 |
Animal.many_to_one('ZooID', Zoo, 'ID') |
|---|
| 88 |
|
|---|
| 89 |
# Set up a global Arena object. |
|---|
| 90 |
arena = dejavu.Arena() |
|---|
| 91 |
conf = {u'Connect': r"PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=C:\zookeeper.mdb;"} |
|---|
| 92 |
arena.add_store("main", "access", 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 |
[<zookeeper.Animal object at 0x013281F0>, <zookeeper.Animal object at 0x01328150>, |
|---|
| 117 |
<zookeeper.Animal object at 0x01328130>, <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(authorID=author.ID, bookID=self.ID) |
|---|
| 216 |
self.sandbox.memorize(a) |
|---|
| 217 |
|
|---|
| 218 |
def author_names(self): |
|---|
| 219 |
names = [] |
|---|
| 220 |
for authorship in self.Authorship(): |
|---|
| 221 |
author = authorship.Author() |
|---|
| 222 |
if author: |
|---|
| 223 |
names.append(author.name) |
|---|
| 224 |
return u', '.join(names) |
|---|
| 225 |
|
|---|
| 226 |
class Publisher(Unit): |
|---|
| 227 |
name = UnitProperty(str) |
|---|
| 228 |
|
|---|
| 229 |
class Author(Unit): |
|---|
| 230 |
name = UnitProperty(str) |
|---|
| 231 |
|
|---|
| 232 |
class Authorship(Unit): |
|---|
| 233 |
authorID = UnitProperty(int) |
|---|
| 234 |
bookID = UnitProperty(int) |
|---|
| 235 |
|
|---|
| 236 |
Book.many_to_one('publisher', Publisher, 'ID') |
|---|
| 237 |
Authorship.many_to_one('bookID', Book, 'ID') |
|---|
| 238 |
Authorship.many_to_one('authorID', Author, 'ID') |
|---|
| 239 |
|
|---|
| 240 |
arena = Arena() |
|---|
| 241 |
arena.register_all(globals()) |
|---|
| 242 |
</pre> |
|---|
| 243 |
|
|---|
| 244 |
|
|---|
| 245 |
<p>The deployer would write in a .conf file:</p> |
|---|
| 246 |
<pre>[Books] |
|---|
| 247 |
Class: dejavu.storage.storepypgsql.StorageManagerPgSQL |
|---|
| 248 |
Connect: host=localhost dbname=bookstore user=postgres password=****</pre> |
|---|
| 249 |
|
|---|
| 250 |
<p>To create the tables:</p> |
|---|
| 251 |
<pre>for cls in (Author, Publisher, Book): |
|---|
| 252 |
arena.create_storage(cls)</pre> |
|---|
| 253 |
|
|---|
| 254 |
<p>The app developer's runtime code reads as follows:</p> |
|---|
| 255 |
<pre> |
|---|
| 256 |
box = arena.new_sandbox() |
|---|
| 257 |
ppython = Book(title='Programming Python', price=20, |
|---|
| 258 |
publishDate=datetime.datetime(2001, 3, 1)) |
|---|
| 259 |
# This next line is redundant; all properties default to None. |
|---|
| 260 |
# But explicitness is rarely a bad thing. |
|---|
| 261 |
ppython.publisher = None |
|---|
| 262 |
box.memorize(ppython) |
|---|
| 263 |
|
|---|
| 264 |
print ppython.title # output: 'Programming Python' |
|---|
| 265 |
|
|---|
| 266 |
mlutz = Author(name = 'Mark Lutz') |
|---|
| 267 |
box.memorize(mlutz) # give mlutz an ID |
|---|
| 268 |
ppython.addAuthor(mlutz) |
|---|
| 269 |
|
|---|
| 270 |
print len(ppython.Authorship()) # output: 1 |
|---|
| 271 |
print ppython.author_names() # output: 'Mark Lutz' |
|---|
| 272 |
|
|---|
| 273 |
oreilly = Publisher(name="O'Reilly") |
|---|
| 274 |
box.memorize(oreilly) # give oreilly an ID |
|---|
| 275 |
|
|---|
| 276 |
ppython.publisher = oreilly.ID |
|---|
| 277 |
print ppython.Publisher().name # output: "O'Reilly" |
|---|
| 278 |
|
|---|
| 279 |
print len(oreilly.Book()) # output: 1 |
|---|
| 280 |
|
|---|
| 281 |
print 'Hi,', oreilly.Book().author_names() # output: "Hi, Mark Lutz" |
|---|
| 282 |
</pre> |
|---|
| 283 |
</p> |
|---|
| 284 |
|
|---|
| 285 |
<hr /> |
|---|
| 286 |
|
|---|
| 287 |
<p><a name='fowler'>[1]</a> Fowler, |
|---|
| 288 |
<a href='http://www.martinfowler.com/eaaCatalog/identityMap.html'>Patterns |
|---|
| 289 |
of Enterprise Application Architecture</a>.<br /> |
|---|
| 290 |
<a name='cpython'>[2]</a> Dejavu relies upon bytecode hacking to achieve |
|---|
| 291 |
its clean lambda syntax for data queries. Therefore, it is CPython-specific. |
|---|
| 292 |
In addition, the bytecode of Python may change from one version of Python |
|---|
| 293 |
to another; if you find your version of Python does not work with Dejavu's |
|---|
| 294 |
<tt>codewalk</tt> and <tt>logic</tt> modules, please let me know.<br /> |
|---|
| 295 |
</p> |
|---|
| 296 |
|
|---|
| 297 |
</body> |
|---|
| 298 |
</html> |
|---|