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/modeling.html

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

Bah. datetime.datetime is only stored at 1-second resolution.

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: Modeling your Application</title>
8     <link href='dejavu.css' rel='stylesheet' type='text/css' />
9 </head>
10
11 <body>
12
13 <h2>Application Designers: Using Dejavu to Construct a Domain Model</h2>
14
15 <h3>Units</h3>
16 <p>When constructing a Domain Model for your application, you will want
17 to distinguish between objects that will be persisted and objects that
18 will not. By registering a subclass of <tt>dejavu.Unit</tt>, you allow
19 instances of that subclass to be persisted.</p>
20
21 <p>Before you can register your Unit class, you must create it:
22 <pre>import dejavu
23 class Printer(dejavu.Unit): pass</pre>
24 This is all you need for a fully-functioning Unit class. There are
25 no methods or attributes that you are required to override; simply
26 subclass from <tt>Unit</tt>. However, this is a fairly uninteresting
27 class. It automatically has an ID property, but doesn't provide any
28 functionality other than what <tt>Unit</tt> already provides. The first
29 thing we will probably want to add to our new class is persistent data.</p>
30
31 <h4>UnitProperty</h4>
32 <p>Once you have defined a persistent class (by subclassing <tt>Unit</tt>),
33 you need to make another decision. Rather than persist the entire object
34 <tt>__dict__</tt>, you specify a subset of persistent attributes by using
35 <tt>UnitProperty</tt>, a data descriptor. If you've used Python's builtin
36 property() construct, you've used descriptors before.</p>
37
38 <p>We might enhance our Printer example thusly:
39 <pre>from dejavu import Unit, UnitProperty
40 class Printer(Unit):
41     Manufacturer = UnitProperty(unicode)
42     ColorCopies = UnitProperty(bool)
43     PPM = UnitProperty(float)</pre>
44 This adds three persistent attributes to our <tt>Printer</tt> objects,
45 each with a different datatype. In addition, every subclass of <tt>Unit</tt>
46 inherits an 'ID' property, an int.</p>
47
48 <p>When you get and set <tt>UnitProperty</tt> attributes, they behave just
49 like any other attributes:
50 <pre>>>> p = Printer()
51 >>> p.PPM = 25
52 >>> p.PPM
53 25.0</pre>
54 However, you will notice right away that the int value we provided has been
55 coerced to a float behind the scenes. This is because we specified the PPM
56 attribute as a 'float' type when we created it. The value of a Unit
57 Property is restricted to the type which you specify. The only other valid
58 value for a Unit Property is None; any Property may be None at any time,
59 and in fact, all Properties are None until you assign values to them:
60 <pre>>>> p.ColorCopies is None
61 True</pre></p>
62
63 <h5>datetime.datetime</h5>
64 <p>If you use <tt>datetime.datetime</tt> for the type of a UnitProperty,
65 most StorageManagers will throw away the microseconds. This is
66 an unfortunate oversight that should be corrected in 1.5.</p>
67
68 <h4>Unit ID's</h4>
69 <p>All Units possess an <tt>identifiers</tt> attribute, a tuple of
70 their UnitProperties which define the uniqueness of a Unit. The
71 <tt>Unit</tt> base class possesses a single Unit Property, an int
72 named 'ID', and its <tt>identifiers</tt> attribute is therefore
73 <tt>(ID,)</tt>. That's not a string in the tuple; it's a reference
74 to the actual UnitProperty class. If you wish to use identifiers
75 of a different number, types, or names, simply replace the
76 <tt>identifiers</tt> attribute in your subclass:</p>
77
78 <pre>class Printer(Unit):
79     # Set ID to None to remove the ID property from this subclass.
80     ID = None
81    
82     Model = UnitProperty(unicode)
83     UnitNumber = UnitProperty(int)
84     identifiers = (Model, UnitNumber)
85 </pre>
86
87 <p>Every Unit must possess at least one identifier. This ensures that
88 each Unit within the system is unique. You should consider any
89 UnitProperty which is one of the identifiers to be read-only
90 after a Unit has been memorized.</p>
91
92 <h4>Creating and Populating Properties</h4>
93 <p>In addition to defining Unit Properties within your class body,
94 you can define them after the class body has been executed via
95 the classmethod <tt class='def'>Unit.set_property()</tt>. For example,
96 the following two classes are equivalent:
97 <pre>class Book(Unit):
98     Content = UnitProperty(unicode)
99
100 class Book(Unit): pass
101 Book.set_property('Content', unicode)</pre>
102
103 Declarations outside of the class body allow more dynamic setting of
104 Unit properties. You can define multiple properties at once via
105 the <tt class='def'>Unit.set_properties()</tt> classmethod:
106
107 <pre>class Book(Unit): pass
108 Book.set_properties({'Content': unicode,
109                      'Publisher': unicode,
110                      'Year': int,
111                      })</pre>
112 </p>
113
114 <p>You also have options when populating Unit Properties. The standard way
115 is simply to reference them as normal Python instance attributes. However,
116 you may also use the <tt class='def'>adjust()</tt> method to modify
117 multiple properties at once; pass in keyword arguments which match the
118 properties you wish to modify. Keyword arguments also work when
119 instantiating the object. For example, the following three code
120 snippets are equivalent:
121
122 <pre>pub = Book()
123 pub.Publisher = 'Walter J. Black'
124 pub.Year = 1928
125
126 pub = Book()
127 pub.adjust(Publisher='Walter J. Black', Year=1928)
128
129 pub = Book(Publisher='Walter J. Black', Year=1928)</pre>
130 </p>
131
132 <h4>Unit Properties are First-Class Objects</h4>
133 <p>Like many descriptors, Unit Properties behave differently when you access
134 them from the class, rather than from an instance as above. When calling
135 them from the class, you receive the <tt>UnitProperty</tt> object itself,
136 rather than its value for a given instance. That is,
137 <pre>>>> c = Printer.ColorCopies
138 >>> c
139 &lt;dejavu.UnitProperty object at 0x01112970></pre>
140 This is significant, because it allows us to store metadata about the
141 property itself:
142 <pre>>>> c.type, c.index, c.hints, c.key
143 (&lt;type 'bool'>, False, {}, 'ColorCopies')</pre>
144
145 When you define a UnitProperty instance, you can pass in these extra
146 attributes. Its signature is <tt class='def'>UnitProperty(type=unicode,
147 index=False, hints={}, key=None, default=None)</tt>. Supply any, all,
148 or none of them as
149 needed. The <tt>key</tt> attribute is merely the property's canonical name,
150 and is usually set for you. The <tt>index</tt> value tells database Storage
151 Managers whether or not to index the column (if they do any indexing). The
152 <tt>type</tt> attribute limits property values to instances of that type
153 (or <tt>None</tt>). Finally, the <tt>hints</tt> dictionary provides hints
154 to Storage Managers to help optimize storage. If you write a custom Storage
155 Manager, you may define and use your own hints. Here are the ones that most
156 builtin SM's understand:</p>
157
158 <table>
159 <tr><th>Key</th><th>Values</th><th>Description</th></tr>
160 <tr>
161     <td>bytes</td>
162     <td>&gt;= 0</td>
163     <td>Inform SMs that would usually store unicode strings as strings of
164         unlimited length, that a particular value should be a smaller
165         object. A value of 0 implies no limit.</td>
166 </tr>
167 <tr>
168     <td>scale</td>
169     <td>&gt;= 0</td>
170     <td>Scale is the number of digits to the right of the decimal point
171         in a NUMERIC (fixedpoint or decimal) field. This hint informs SMs
172         that would usually store such data at a default scale (usually 2),
173         that the property should use a different scale.</td>
174 </tr>
175 <tr>
176     <td>precision</td>
177     <td>&gt;= 0</td>
178     <td>Precision is the total number of digits in a NUMERIC (fixedpoint or
179         decimal) field. This hint informs SMs that would usually store such
180         data at maximum precision, that the property should be a smaller
181         object. A value of 0 (or no hint) implies the maximum that the SM
182         can supply. PostgreSQL, for example, can handle 1000 digits. Note
183         that the <tt>fixedpoint</tt> module uses the word "precision" where
184         we use the word "scale"; it actually has unlimited precision (as
185         we use the word). The <tt>decimal</tt> module, in contrast, has
186         limited precision but no scale.</td>
187 </tr>
188 </table>
189
190
191 <h4>Triggers</h4>
192 <p>Triggers are behaviors which fire when the value of a Unit Property is
193 changed. You can override a UnitProperty's __set__ method to achieve this
194 in Dejavu. For example:
195 <pre>class DatedProperty(UnitProperty):
196     def __set__(self, unit, value):
197         UnitProperty.__set__(self, unit, value)
198         unit.Date = datetime.datetime.now().replace(microsecond=0)
199         parent = unit.Forum()
200         if parent:
201             parent.Date = unit.Date
202
203 class Topic(Unit):
204     Date = UnitProperty(datetime.date)
205     Content = DatedProperty()
206     ForumID = UnitProperty(int)
207
208 class Forum(Unit):
209     Date = UnitProperty(datetime.date)
210
211 Topic.many_to_one('ForumID', Forum, 'ID')</pre>
212 In this example, whenever Topic().Content is set, the <tt>__set__</tt>
213 method will be called and the object's <tt>Date</tt> attribute will
214 be modified. Then, the Topic's parent Forum is looked up and <i>its</i>
215 <tt>Date</tt> is modified.</p>
216
217 <p>As with any trigger system, you need to be careful not to have triggers
218 called out of order. For example, if a user changes both the ForumID and
219 Content properties in a single operation (like a web page submit), the old
220 Forum will be incorrectly modified if the Content property is applied
221 first. I don't have any cool tools built into Dejavu to help you with
222 this, but I'm open to suggestions.</p>
223
224 <h5>TriggerProperty</h5>
225 <p>Dejavu 1.4 has a new <tt class='def'>TriggerProperty</tt> class,
226 which overrides <tt>__set__</tt> for you. If the value in question
227 changes (and the unit has a sandbox), then the
228 <tt class='def'>on_set(unit, oldvalue)</tt> method will be called.
229 Override it in your subclass like this:
230 <pre>
231 class NoteContentProperty(TriggerProperty):
232    
233     def on_set(self, unit, oldvalue):
234         unit.LastModified = datetime.date.today()
235 </pre>
236
237 Note that, if you need to know what the <i>new</i> value is, it's
238 already been set on the unit.</p>
239
240 <h4>Registration of Unit Classes</h4>
241 <p>In addition to defining your Unit class, you must also register that
242 class with your application's <tt>Arena</tt> object. Each class which
243 you want Dejavu to manage must be passed to
244 <tt class='def'>Arena.register(cls)</tt>.
245 If you create a module with multiple classes, you can register them all
246 at once with <tt class='def'>Arena.register_all(globals())</tt>. It will
247 grab any Unit subclasses out of your module's globals() (or any other
248 mapping you pass to <tt>register_all</tt>) and register them.</p>
249
250 <p>The register and register_all methods also register any Associations
251 you have defined between Units.</p>
252
253 <h4>Subclasses and Inheritance</h4>
254 <p>Starting in 1.4, you can use superclasses to recall all subclasses.
255 For example:
256
257 <pre>class Payment(Unit): pass
258 class Cash(Payment): pass
259 class Credit(Payment): pass
260
261 ...time passes and instances of each class are memorized...
262
263 sandbox.recall(Payment)
264 [&lt;bill.Payment object at 0x01158E10>,
265  &lt;bill.Cash object at 0x0118B350>,
266  &lt;bill.Credit object at 0x0118B170>]
267 </pre>
268
269 <p>This also works for the <tt>xrecall</tt> and <tt>unit</tt> methods, but
270 only when providing a single class (none of them retrieve subclasses when
271 recalling joined classes yet). Currently, you cannot reference a property
272 (in an Expression) which is not present in both the superclass and all of
273 the subclasses.</p>
274
275 <h3>Sandboxes</h3>
276 <p>During the life of a client connection, your application should create
277 and use a <tt>Sandbox</tt> to manage the set of "live" Units. A Sandbox
278 manages the in-memory lifecycle of Units: creation, identity, mutation, and
279 destruction. Sandboxes route persistence operations on Units to the correct
280 Storage Manager.</p>
281
282 <p>You can create Sandbox objects directly. They take a single argument, the
283 top-level <tt>Arena</tt> object. Arenas also provide a convenience function,
284 <tt class='def'>new_sandbox</tt>, which does this for you. The following
285 lines are equivalent:
286 <pre>box = dejavu.Sandbox(myArena)
287
288 box = myArena.new_sandbox()</pre>
289 You might often choose the latter when you have a reference to the Arena
290 object, and would rather avoid importing dejavu yet again just to obtain
291 the Sandbox class.</p>
292
293 <a name='memorize'><h4>Memorizing Units</h4></a>
294 <p>When you create a Unit instance, it exists in isolation. There is no
295 connection between that Unit and storage; your Unit will not be persisted,
296 because Dejavu doesn't yet possess a reference to your Unit. To provide
297 that link, you <i>memorize</i> your Unit (or rather, you tell your Sandbox
298 to memorize it):
299 <pre>class Publisher(Unit):
300     City = UnitProperty(unicode)
301
302 p = Publisher(ID='Walter J. Black')
303 box.memorize(p)</pre></p>
304
305 <p>Memorization does several things. First, it places your new Unit into
306 your Arena. That Unit instance will now be persisted by the appropriate
307 Storage Manager. It can be recalled from storage when needed, using the
308 built-in Expression syntax. It may have been given an ID (see
309 <u>Sequencing</u>, below). Memorization also makes your Unit
310 <i>concrete</i>; that is, your Unit will now possess a <tt>sandbox</tt>
311 attribute. Units whose <tt>sandbox</tt> attribute is not set (is None)
312 have no relationships, and their Unit Property triggers (if any) will
313 not fire.</p>
314
315 <p>You may define special methods on your Units to provide start-of-life
316 behaviors. If a Unit possesses an <tt>on_memorize</tt> method, it will
317 be called after the Unit has been 'reserved' in storage, and after the
318 Unit has been placed in the Sandbox cache.</p>
319
320 <h4>Sequencing</h4>
321 <p>Every <tt>Unit</tt> has one or more identifiers. The default ID property
322 is of type <tt>int</tt>; however, you can override that to whatever type
323 you like. As long as you provide your own identifier values for Units,
324 nothing will break--you can memorize and recall Units without problems.
325 However, if you memorize a Unit with an ID of <tt>None</tt>, the Sandbox
326 may attempt to provide an ID for it.</p>
327
328 <p>The <tt>Unit</tt> base class possesses a <tt>sequencer</tt> attribute
329 to help Sandboxes generate new IDs. The default value is an instance of
330 <tt>UnitSequencerInteger</tt>, which examines all existing Units, finds
331 the maximum integer ID, adds 1, and uses that value for the new ID.</p>
332
333 <p>The other useful Sequencer is <tt>UnitSequencerNull</tt>, which simply
334 raises an error when asked to generate an ID. If you set <tt>ClassA.ID</tt>
335 to a string or unicode type, you'll probably want to set
336 <tt>ClassA.sequencer = dejavu.UnitSequencerNull()</tt>, and form ID values
337 in your own code.</p>
338
339 <h4>Recalling</h4>
340 <p>Once you have memorized a Unit or two, you will probably want to
341 recall them at some point. Sandboxes possess four member functions to
342 accomplish this.</p>
343
344 <h5>recall()</h5>
345 <p>First, the appropriately named <tt class='def'>recall(cls, expr)</tt> function.
346 This is the full-blown query method. As a first argument, you pass it the
347 class (<b>not</b> the name of the class, but the actual class) of which you
348 expect to retrieve instances. The second argument should be an instance
349 of <tt>dejavu.logic.Expression</tt>, an object which encapsulates your
350 specific query (see <a href='managing.html#Querying'>Querying</a>).
351 An example recall operation:
352 <pre>>>> e = logic.Expression(lambda x: x.Year == 1928)
353 >>> units = box.recall(Book, e)
354 >>> [x.Title for x in units]
355 [u'The Giant Horse of Oz', u'Kai Lung Unrolls His Mat',
356  u'Tarzan, The Lord of the Jungle']
357 </pre>
358 If you do not supply an Expression, all Units of the given Unit class
359 will be retrieved in a list.</p>
360
361 <p>If your Unit class defines an <tt>on_recall()</tt> method, it will be
362 called when each Unit has been loaded from storage (at the end of the
363 recall process). Once the unit is loaded into a Sandbox, however,
364 <tt>on_recall</tt> will not be called; it's only called at the Sandbox/SM
365 boundary. If <tt>on_recall</tt> raises <tt>UnrecallableError</tt>, the
366 unit will not be yielded back to the caller, nor placed in the Sandbox
367 cache.</p>
368
369 <h5>Recalling multiple classes at once (JOINs)</h5>
370
371 <p>In addition to providing a single class to <tt>recall</tt>, you have
372 the option of providing a tree of classes, a nested set of
373 <tt class='def'>UnitJoin(class1, class2, leftbiased=None)</tt>
374 instances.</p>
375
376
377 <p>The "leftbiased" argument specifies how the results will be
378 joined:</p>
379
380 <table>
381 <tr><th>leftbiased</th><th>Join Type</th><th>Description</th><th>Operator</th></tr>
382 <tr>
383     <td>None</td>
384     <td>Inner Join</td>
385     <td>All related pairs of both classes will be returned.</td>
386     <td><tt>&</tt> or <tt>+</tt></td>
387 </tr>
388 <tr>
389     <td>True</td>
390     <td>Left Join</td>
391     <td>All related pairs of both classes will be returned. In addition,
392         if any Unit in class1 has no match in class2, we return a single
393         row with Unit1 and a "null Unit" (a Unit, all of whose properties
394         are None).</td>
395     <td><tt>&lt;&lt;</tt></td>
396 </tr>
397 <tr>
398     <td>False</td>
399     <td>Right Join</td>
400     <td>All related pairs of both classes will be returned. In addition,
401         if any Unit in class2 has no match in class1, we return a single
402         row with a "null Unit" (a Unit, all of whose properties are None)
403         and Unit2.</td>
404     <td><tt>&gt;&gt;</tt></td>
405 </tr>
406 </table>
407
408 <p>Look hard? Fear not. There's a <b>much</b> easier way to join units than
409 writing a big tree of UnitJoins. Use the &amp;, &lt;&lt;, and &gt;&gt;
410 operators directly with Unit classes:</p>
411
412 <pre>tree = (Book &lt;&lt; Publisher) & Author</pre>
413
414 <p>This example will automatically produce a UnitJoin tree for you,
415 with Book 'left joined' to Publisher, and then 'inner joined' to
416 Author.</p>
417
418 <p>When you provide multiple classes, the <tt>recall</tt> method returns
419 a list of rows. Each row will be a list of units, one per class in the
420 <tt>classes</tt> arg. The <tt>expr</tt> arg should be a
421 <tt>logic.Expression</tt> which can evaluate all of the
422 units in any given row at once.
423
424 <pre>pubs = box.recall(Publisher & Book,
425                   logic.Expression(lambda p, b: p.ID == 4))</pre>
426
427 This example will retrieve a series of [Publisher, Book] pairs.
428 Note that all three constructs (the UnitJoins, the lambda, and
429 the resulting rows) have the same classes listed in order from
430 left to right.</p>
431
432 <p>In database terminology, this technique performs a series of joins
433 between each pair of classes in your UnitJoin tree. However, repeated units
434 in the results will reference the same object; in the example above, each
435 Publisher unit will be the same object, since we limited that expression to
436 a single Publisher. So we might examine multiple rows in the "pubs" list,
437 but the first unit in each row will be the same unit instance.</p>
438
439 <p>The relationships (joins) between each class are specified by
440 Unit Associations (see <a href='#associations'>below</a>).</p>
441
442 <h5>xrecall()</h5>
443 <p>Just like recall, but returns an iterator instead of a list. Use xrecall
444 to load Units in a more lazy fashion.</p>
445
446 <h5>unit()</h5>
447 <p>The <tt>recall</tt> method can be verbose. When you want a one-liner
448 and only expect a single Unit, use the <tt class='def'>unit(cls, **kw)</tt> method
449 of Sandboxes. Again, you pass the class of Units you wish to retrieve
450 as the first argument. Then, supply keyword arguments of the form
451 "property_name=value". The method will form an equivalent Expression
452 for you from the keyword args. For example:
453 <pre>>>> book = box.unit(Book, ID=1)
454 >>> if book:
455 ...     print book.Title
456 u'Ladies in Hades'</pre>
457 If a Unit is not found that matches the criteria, None is returned.
458 If multiple Units match the criteria, only the first one is returned
459 (although the rest are probably loaded into memory).</p>
460
461 <h5>"Magic recaller" methods</h5>
462 <p>For each class you have registered with your Arena, the Sandbox will
463 have a "magic recaller" method of the same name, to make single-unit
464 lookups easier. Instead of the above example for <tt>box.unit()</tt>,
465 we might just as well have written:
466 <pre>>>> book = box.Book(1)</pre>
467 Note that for the magic methods, unlike for the <tt>unit</tt> method,
468 you may pass identifiers as positional arguments. If the class has
469 multiple identifiers, you should probably stick to keyword arguments;
470 otherwise, you must remember the order of the class' identifiers tuple.
471 </p>
472
473 <h4>Forgetting and Repressing</h4>
474 <p>To <i>forget</i> a Unit is to destroy it forever. You have two options
475 for forgetting Units: you can call <tt>Sandbox().forget(unit)</tt> or
476 the simpler version, <tt class='def'>Unit().forget()</tt>. Either of these will clear
477 the Unit from the Sandbox' cache, and the Sandbox will tell the appropriate
478 Storage Manager to destroy the stored Unit data. If a Unit has not yet
479 been memorized, you do not need to forget it.</p>
480
481 <p>In some circumstances, you may wish to only clear the Unit from the
482 Sandbox without destroying it. You can do this by calling either
483 <tt>Sandbox().repress(unit)</tt> or the simpler version,
484 <tt class='def'>Unit().repress()</tt>.</p>
485
486 <p>You may define special methods on your Units to provide end-of-life
487 behaviors. If a Unit possesses an <tt>on_forget</tt> method, it will
488 be called after the Unit has been destroyed. If a Unit possesses an
489 <tt>on_repress</tt> method, it will be called <i>before</i> the Unit
490 has been repressed. I'm sure there was a good reason for this
491 disparity, but I've forgotten (or perhaps repressed) it.</p>
492
493 <p>Be aware that many of the things you put in an <tt>on_repress</tt>
494 handler might also need to go into <tt>on_forget</tt>. The one doesn't
495 call the other automatically, because sometimes you <i>don't</i> want
496 the same behavior.</p>
497
498
499 <h4>Flushing Sandboxes</h4>
500 <p>When the client connection has closed, you should <i>flush</i> the
501 Sandbox caches. In general, a single call to
502 <tt class='def'>Sandbox().flush_all()</tt>
503 will do the trick. Notice that <tt>flush_all()</tt> calls any
504 <tt>on_repress()</tt> handler for each Unit in the Sandbox.</p>
505
506 <p><b>Warning:</b> You should <b>NOT</b> call <tt>flush_all()</tt>
507 indiscriminately. You will rapidly get into concurrency trouble. You
508 can stay out of trouble following an easy rule: call <tt>flush_all</tt>
509 only at the end of your client's connection.</p>
510
511 <p>If you want the "hard" rule, here it is. If you flush any Unit class,
512 then any instances of that class hanging around need to be re-recalled. If
513 you don't re-recall them, then any changes you make to the old instance
514 won't be saved on the next flush, since flushing only iterates through
515 units in the sandbox. For example, if you do this:</p>
516
517 <pre>box = arena.new_sandbox()
518 thing = box.unit(Thing)
519 box.flush_all()
520 thing.Size += 12
521 box.flush_all()
522 </pre>
523
524 <p>...then the change you make to "Size" won't be persisted, since the
525 Thing object is no longer in the sandbox--it's been flushed out. You have
526 to recall it somehow to get it stuck in the sandbox again. You could go
527 through all kinds of gyrations to save the old units directly to storage,
528 but don't bother. Just get new references to them and save yourself a lot
529 of headache.</p>
530
531 <h4>Views</h4>
532 <p>Sandboxes provide a <tt class='def'>view(cls, attrs, expr=None)</tt> function.
533 This returns values, rather than Units. Put simply, it yields all values
534 for the given attribute(s) of the Unit class provided; each unit will
535 yield a tuple of its values in the same order as the <tt>attrs</tt>
536 sequence you provide. Providing an expr argument (an <tt>Expression</tt>
537 object, see below) will filter the set of Units before obtaining the
538 value tuples.</p>
539
540 <pre>>>> v = sandbox.view(zoo.Animal, ['Name', 'Lifespan'])
541 >>> [row for row in v]    # or list(v), or iterate over v...
542 [('Leopard', 73.5),
543  ('Slug', .75),
544  ('Tiger', None),
545  ('Lion', None),
546  ('Bear', None),
547  ('Ostrich', 103.2),
548  ('Centipede', None),
549  ('Emperor Penguin', None),
550  ('Adelie Penguin', None),
551  ('Millipede', None)
552  ]
553 </pre>
554
555 <p>In this example (pulled from the "zoo" test suite), we grab the name
556 and Lifespan for each Animal. The <tt>attrs</tt> argument must always
557 be an iterable.</p>
558
559 <p>Sandboxes also provide a <tt class='def'>distinct(cls, attrs, expr=None)</tt>
560 function. This works just like <tt>view()</tt>, but returns distinct
561 tuples rather than all tuples.</p>
562
563 <p>The <tt>distinct</tt> function can also be used as a <tt>count</tt>
564 function by passing <tt>attrs = [prop.key for prop in cls.identifiers]</tt>.
565 Sandboxes provide a <tt class='def'>count(cls, expr)</tt> method which does
566 just this.</p>
567
568
569 <a name='associations'><h3>Associations between Unit Classes</h3></a>
570 <p>Once you've put together some Unit classes, chances are you're going to
571 want to associate them. Generally, this is accomplished by creating a
572 property in the Unit_B class which stores IDs of Unit_A objects (which
573 might be called <i>foreign keys</i> in a database context).
574 <pre>class Archaeologist(Unit):
575     Height = UnitProperty(float)
576
577 class Biography(Unit):
578     ArchID = UnitProperty(int)
579     PubDate = UnitProperty(datetime.date)</pre>
580 In this example, each <tt>Biography</tt> object will have an <tt>ArchID</tt>
581 attribute, which will equal the <tt>ID</tt> of some <tt>Archaeologist</tt>.
582 In Dejavu terms, we say that there is a <i>near class</i> (with a <i>near
583 key</i>) and a <i>far class</i> (with a <i>far key</i>). Associations in
584 Dejavu are not one-way, so it doesn't matter which class you choose for the
585 "near" one and which for the "far" one.</p>
586
587 <p>You could stop at this point in your design, and simply remember what
588 these keys are and how they relate, and manipulate them accordingly. But
589 Dejavu allows you to explicitly declare these associations:
590 <pre>Archaeologist.one_to_many('ID', Biography, 'ArchID')</pre>
591 You pass in the the near key, the far class, and the far key.
592 There are similar methods for one_to_one and many_to_one. In addition,
593 there is a Unit.associate method which allows you to use your own
594 relationship objects.</p>
595
596 <p>What does an explicit association buy for you? First, you can associate
597 Units without having to remember which keys are related. Second, Arenas
598 discover associations and fill the <tt>Arena.associations</tt> registry, so
599 that smart consumer code (like <a href='managing.html#unitenginerules'>Unit
600 Engine Rules</a>) can automatically follow association paths for you.
601 Third, each Unit class has a private <tt>_associations</tt> attribute,
602 a <tt>dict</tt>. Each Unit involved in in the association gains an entry
603 in that dict: the key is the far class name,
604 and the value is a UnitAssociation instance, a non-data (method) descriptor,
605 with additional nearClass, nearKey, farClass, farKey, and to_many attributes.</p>
606
607 <h4><tt>Unit.add()</tt></h4>
608 <p>Once two classes have been associated, you attach Unit <i>instances</i>
609 to each other by equating their associated properties. That was a
610 mouthful. Here's an example:
611 <pre>>>> evbio = Biography()
612 >>> evbio.ArchID = Eversley.ID
613 </pre>
614 The two unit <i>instances</i> (evbio and Eversley) are now associated
615 (only their <i>classes</i> were before). Keep in mind that many Unit
616 instances need to be memorized in order to obtain an ID.</p>
617
618 <p>Rather than forcing you to remember all of the related classes and keys,
619 Dejavu Units all have an <tt>add</tt> method, which does the same thing:
620 <pre>>>> evbio = Biography()
621 >>> evbio.add(Eversley)
622 </pre>
623 The <tt>add</tt> method works in either direction, so you could just as
624 well write:
625 <pre>>>> evbio = Biography()
626 >>> Eversley.add(evbio)
627 </pre>
628 The <tt>add</tt> method will take any number of unit instances as
629 arguments, and add each one in turn. That is:
630 <pre>
631 >>> evbio1 = Biography()
632 >>> evbio2 = Biography()
633 >>> evbio3 = Biography()
634 >>> Eversley.add(evbio1, evbio2, evbio3)
635 </pre>
636 </p>
637
638 <h4>"Related units" methods</h4>
639 <p>To make querying easier, each of the two Unit classes will gain a new
640 "related units" method which simplifies looking up related instances
641 of the other class. The new method for Unit_B will have the name of Unit_A,
642 and vice-versa. In our example:
643 <pre>>>> Archaeologist.Biography
644 &lt;unbound method Archaeologist.related_units>
645 >>> Eversley = Archaeologist(Height=6.417)
646 >>> Eversley.Biography
647 &lt;bound method Archaeologist.related_units of &lt;__main__.Archaeologist
648 object at 0x011A1930>>
649 >>> bios = Eversley.Biography()
650 >>> bios
651 [&lt;arch.Biography object at 0x01158E10>,
652  &lt;arch.Biography object at 0x0118B350>,
653  &lt;arch.Biography object at 0x0118B170>]
654 >>> evbio1.Archaeologist()
655 &lt;__main__.Archaeologist object at 0x011A1930>
656 </pre>
657 We've only created three Biographies at this point, so we can print the list
658 easily. At the other extreme (when you have hundreds of Biographies to filter),
659 you can pass an optional <tt>Expression</tt> object to the "related units" method.
660 When you do, the list of associated Units will be filtered accordingly.</p>
661
662 <p>Notice that, because our relationship is one-to-many, <b>the two
663 "related units" methods behave differently</b>. The "one"
664 (Archaeologist) which is retrieving the "many" (Biography) retrieves
665 a list. The "many" retrieving the "one" retrieves a single Unit.
666 When retrieving "to-one", the result will always be a single Unit
667 (or None if there is no matching Unit). When retrieving "to-many",
668 the result will always be a list, (it will be empty if there are
669 no matches). This is new for Dejavu 1.4 (previously, they both
670 would have returned lists).</p>
671
672 <p>Because the "related units" method names are formed automatically, you need
673 to take care not to use the names of Unit classes for your Unit properties.
674 In our example, we used "ArchID" for the name of our "foreign key". If we
675 had used "Archaeologist" instead, we would have had problems; when we
676 associated the classes, the <i>property</i> named "Archaeologist" would
677 have collided with the <i>"related units" method</i> named "Archaeologist".
678 Be careful when naming your properties, and plan for the future. The best
679 approach is probably to end your property name with "ID" every time.</p>
680
681 <p>Unlike some other ORM's, Dejavu doesn't cache far Units within the near
682 Unit. Each time you call the "related units" method, the data is recalled
683 from your Sandbox. It is quite probable that those far Units are still
684 sitting in memory in the Sandbox, but they're not going to persist in
685 the near Unit itself in any way.</p>
686
687 <p>Finally, some of you may want to override the default "related units"
688 methods. Feel free; <tt>Unit.associate</tt> takes two optional arguments,
689 which should be subclasses of the UnitAssociation descriptor. See the
690 source code for more information.</p>
691
692 <h4>Custom Unit Associations</h4>
693
694 <p>Sometimes you need an association between two classes that is more complicated.
695 For example, you might have an Archaeologist object and want to retrieve
696 just their <i>last</i> Biography. Here's an example of how to do this:
697 <pre>class LastBiographyAssociation(dejavu.UnitAssociation):
698     """Unit Association to relate an Archaeologist to their last Biography."""
699    
700     to_many = False
701     register = False
702    
703     def related(self, unit, expr=None):
704         bios = unit.Biography()
705         if bios:
706             bios.sort(dejavu.sort("PubDate"))
707             return bios[-1]
708         return None
709
710 descriptor = LastBiographyAssociation(u'ID', Biography, u'ID')
711 descriptor.nearClass = Archaeologist
712 Archaeologist._associations["Last Biography"] = descriptor</pre>
713
714 There are a couple of things to note, here. We are basically doing by
715 hand what the <tt>associate</tt> method does for you automatically, but
716 that method makes <i>two</i> associations (one in each direction), and
717 we're only making one. The <tt class='def'>related(unit, expr)</tt>
718 method is overridden to do the actual lookup of far units. Because the
719 <tt>to_many</tt> attribute is False, <tt>related</tt> returns a single
720 Unit, or None. Finally, the <tt>register</tt> attribute, when False,
721 keeps the Arena from registering this association in its graph
722 (see <a href='#registering'>Registering</a>, below).</p>
723
724 <h3>The Arena Object</h3>
725 <p>The topmost class in Dejavu is the <tt>Arena</tt> class. When building
726 a Dejavu application, you must first create an instance of this class,
727 and must find a way to persist this object across client connections.
728 This can be achieved in multiple ways; web applications, for example,
729 will typically create a single process to serve all requests. Desktop
730 applications will probably create a single Arena object for each
731 running instance of the program.</p>
732
733 <h4>Loading Stores</h4>
734 <p>You <b>may</b> manually set up Storage Managers by calling
735 <tt>Arena().add_store(name, store, options)</tt>. But, you
736 probably shouldn't. Instead, allow your deployers to decide for
737 themselves which storage solution(s) to use. You can do this by calling
738 <tt>load(filename)</tt>; pass it the filename of an INI-style file
739 which your deployers can tweak without screwing up your Python code.
740 The <a href='storage.html'>next chapter</a> in this reference is completely
741 devoted to educating deployers; point them to it or copy/modify it in your
742 own release docs.</p>
743
744 <a name='registering'><h4>Registering Unit Classes</h4></a>
745 <p>The <tt>Arena</tt> object maintains a registry of Unit classes
746 (<tt>._registered_classes</tt>). You shouldn't manipulate this structure
747 on your own; instead, use the <tt>register</tt> or <tt>register_all</tt>
748 methods to register each Unit class.</p>
749
750 <p>The <tt>Arena</tt> object also manages the associations between Unit
751 classes in its <tt>associations</tt> attribute, which is a simple,
752 unweighted, undirected graph. Whenever you register a class, the Arena
753 will add its associations to this graph. The only other common operation
754 is to call <tt>.associations.shortest_path(start, end)</tt>, to retrieve
755 the chain of associations between two Unit classes.</p>
756
757 <h4>Managing Schema Changes</h4>
758 <p>The <tt>Schema</tt> class helps you manage changes to your Dejavu model
759 throughout its lifetime. For example, let's say that we deploy our
760 Archaeology-Biography application at various libraries around the world.
761 After a year, one of the developers wishes to implement a new reporting
762 feature; however, it would be easiest to build if the Unit Property names
763 could be exposed to the users. Unfortunately, our "ArchID" property on the
764 Biography class isn't very informative. It would be better if we could
765 rename that to "ArchaeologistID":</p>
766
767 <pre>class ArchBioSchema(dejavu.Schema):
768    
769     guid = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
770     latest = 2
771    
772     def upgrade_to_2(self):
773         self.arena.rename_property(Biography, "ArchID", "ArchaeologistID")
774
775 abs = ArchBioSchema(arena)
776 abs.upgrade()
777 </pre>
778
779 <p>Assuming we've already made the change to our model, the above example
780 renames the property in the persistence layer (the database). There are
781 also <tt class='def'>add_property(cls, name)</tt> and
782 <tt class='def'>drop_property(cls, name)</tt> methods. There may be more
783 methods in future versions of Dejavu; this feature is pretty new.</p>
784
785 <p>The example also declares this change to be "version 2" of our schema.
786 If you examine the base Schema class, you will see that it already has an
787 <tt>upgrade_to_0</tt> method. The "zeroth" upgrade makes no schema changes;
788 it merely marks all deployed databases with "version 0". I skipped version
789 1 in the example, just in case I need some setup code in the future ;).</p>
790
791 <p>If you call <tt class='def'>upgrade(version)</tt> with a version argument,
792 then your deployment will be upgraded to that version. If no argument is
793 given, the installation will be upgraded to <tt>Schema.latest</tt>. You
794 can even skip steps (i.e. remove methods for broken steps) if it comes to
795 that.</p>
796
797 <p>The Schema superclass also has a <tt class='def'>stage</tt> attribute.
798 While an upgrade is in process, this value will be an int, the same number
799 as that of the upgrade method. That is, while upgrade_to_2 is running,
800 <tt>stage</tt> will be 2. If no upgrade method is running, <tt>stage</tt>
801 will be None.</p>
802
803 <p>After you run <tt>upgrade</tt>, you can call the
804 <tt class='def'>assert_storage</tt> method to tell Dejavu to create storage
805 (tables in your database) for all the Unit classes registered in your arena.
806 If storage already exists for a given class, it is skipped.</p>
807
808 <p><b>Please note:</b> the installed version defaults to "latest". This
809 allows new installs to skip all the upgrade steps, and just use the latest
810 class definitions when they call <tt>assert_storage</tt>. However, it means
811 that if you deploy your apps for a while without a Schema, and then
812 introduce one later, you must manually decrement DeployedVersion from
813 "latest" to the actual deployed version *before* running your app for the
814 first time (or things will break due to the difference between the latest
815 and deployed schema).</p>
816
817 <h5>The DeployedVersion Unit</h5>
818
819 <p>The <tt>Schema</tt> class uses a magic table in the database to keep
820 track of each deployment's schema version. The Unit class is called
821 "DeployedVersion", and it has ID and Version attributes.</p>
822
823 <p>The ID attribute will be set to whatever your <tt>Schema.guid</tt>
824 is. It's a simple way to isolate multiple installed Dejavu applications.
825 A given application should use the same guid throughout its lifetime.
826 I used <tt>sha.new().hexdigest()</tt> to generate the example. Feel free
827 to use sha.new, a guid generator, a descriptive name, or whatever you
828 like.</p>
829
830
831 <hr />
832
833 </body>
834 </html>
Note: See TracBrowser for help on using the browser.