Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

root/branches/crazycache/dejavu/doc/modeling.html

Revision 543 (checked in by fumanchu, 5 years ago)

Changed to unit(cls, **kwargs) sig throughout. Also changed sandbox.recall to include order, limit, and offset args; removed **kwargs from recall, but the 'expr' arg may now be a dict for all x/multi/recall methods throughout. Removed inheritance code. Added a 'sum' method to StorageManager?.

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 <a name='units'><h3>Units</h3></a>
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 an
66 unfortunate oversight that should be corrected sometime in the future.</p>
67
68 <h4>Unit ID's</h4>
69 <p>All Units possess an <tt>identifiers</tt> attribute, a tuple of
70 their UnitProperty names 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 a string in the tuple; older versions of Dejavu
74 used a reference to the actual UnitProperty class instead. If you wish
75 to use identifiers
76 of a different number, types, or names, simply replace the
77 <tt>identifiers</tt> attribute in your subclass:</p>
78
79 <pre>class Printer(Unit):
80     # Set ID to None to remove the ID property from this subclass.
81     ID = None
82    
83     Model = UnitProperty(unicode)
84     UnitNumber = UnitProperty(int)
85     identifiers = ('Model', 'UnitNumber')
86 </pre>
87
88 <p>Every Unit should possess at least one identifier. This ensures that
89 each Unit within the system is unique. You should consider any
90 UnitProperty which is one of the identifiers to be read-only
91 after a Unit has been memorized. Extremely rare applications
92 (like write-only log tables) are allowed to use en empty identifiers
93 tuple, but in most OLTP/OLAP scenarios, all your Units should
94 have at least one identifier property.</p>
95
96 <h4>Creating and Populating Properties</h4>
97 <p>In addition to defining Unit Properties within your class body,
98 you can define them after the class body has been executed via
99 the classmethod <tt class='def'>Unit.set_property()</tt>. For example,
100 the following two classes are equivalent:
101 <pre>class Book(Unit):
102     Content = UnitProperty(unicode)
103
104 class Book(Unit): pass
105 Book.set_property('Content', unicode)</pre>
106
107 Declarations outside of the class body allow more dynamic setting of
108 Unit properties. You can define multiple properties at once via
109 the <tt class='def'>Unit.set_properties()</tt> classmethod:
110
111 <pre>class Book(Unit): pass
112 Book.set_properties({'Content': unicode,
113                      'Publisher': unicode,
114                      'Year': int,
115                      })</pre>
116 </p>
117
118 <p>You also have options when populating Unit Properties. The standard way
119 is simply to reference them as normal Python instance attributes. However,
120 you may also use the <tt class='def'>adjust()</tt> method to modify
121 multiple properties at once; pass in keyword arguments which match the
122 properties you wish to modify. Keyword arguments also work when
123 instantiating the object. For example, the following three code
124 snippets are equivalent:
125
126 <pre>pub = Book()
127 pub.Publisher = 'Walter J. Black'
128 pub.Year = 1928
129
130 pub = Book()
131 pub.adjust(Publisher='Walter J. Black', Year=1928)
132
133 pub = Book(Publisher='Walter J. Black', Year=1928)</pre>
134 </p>
135
136 <h4>Unit Properties are First-Class Objects</h4>
137 <p>Like many descriptors, Unit Properties behave differently when you access
138 them from the class, rather than from an instance as above. When calling
139 them from the class, you receive the <tt>UnitProperty</tt> object itself,
140 rather than its value for a given instance. That is,
141 <pre>>>> c = Printer.ColorCopies
142 >>> c
143 &lt;dejavu.UnitProperty object at 0x01112970></pre>
144 This is significant, because it allows us to store metadata about the
145 property itself:
146 <pre>>>> c.type, c.index, c.hints, c.key
147 (&lt;type 'bool'>, False, {}, 'ColorCopies')</pre>
148
149 When you define a UnitProperty instance, you can pass in these extra
150 attributes. Its signature is <tt class='def'>UnitProperty(type=unicode,
151 index=False, hints={}, key=None, default=None)</tt>. Supply any, all,
152 or none of them as
153 needed. The <tt>key</tt> attribute is merely the property's canonical name,
154 and is usually set for you. The <tt>index</tt> value tells database Storage
155 Managers whether or not to index the column (if they do any indexing). The
156 <tt>type</tt> attribute limits property values to instances of that type
157 (or <tt>None</tt>). Finally, the <tt>hints</tt> dictionary provides hints
158 to Storage Managers to help optimize storage. If you write a custom Storage
159 Manager, you may define and use your own hints. Here are the ones that most
160 builtin SM's understand:</p>
161
162 <table>
163 <tr><th>Key</th><th>Values</th><th>Description</th></tr>
164 <tr>
165     <td>bytes</td>
166     <td>b &gt;= 0</td>
167     <td>Inform SMs that a particular property will never exceed <i>b</i>
168         bytes. This applies to <tt>long</tt> and <tt>int</tt> properties,
169         as well as <tt>str</tt> and <tt>unicode</tt>. A value of 0 implies
170         no limit. If not specified, a default maximum will be used. Many
171         database backends default to 255; non-database backends often have
172         no limit. Check your Storage Manager.</td>
173 </tr>
174 <tr>
175     <td>scale</td>
176     <td>s &gt;= 0</td>
177     <td>Scale is the number of digits to the right of the decimal point
178         in a NUMERIC (fixedpoint or decimal) field. This hint informs SMs
179         that would usually store such data at a default scale (usually 2),
180         that the property should use a different scale.</td>
181 </tr>
182 <tr>
183     <td>precision</td>
184     <td>p &gt;= 0</td>
185     <td>Precision is the total number of <b>decimal</b> digits in a NUMERIC
186         (<tt>fixedpoint</tt> or <tt>decimal</tt>) field, or the total
187         number of <b>binary</b> digits in a <tt>float</tt> field.
188         This hint informs SMs that the property will never exceed
189         <i>p</i> digits. If missing, the StorageManager will supply a
190         default maximum precision. For example, PostgreSQL can handle
191         1000 decimal digits. If explicitly set to 0 (zero), the
192         StorageManager will allow unlimited precision, if possible.
193         Note that the <tt>fixedpoint</tt> module uses the word
194         "precision" where we use the word "scale"; it actually has
195         unlimited precision (as we use the word). The <tt>decimal</tt>
196         module, in contrast, uses limited precision but no scale.</td>
197 </tr>
198 </table>
199
200
201 <h4>Triggers</h4>
202 <p>Triggers are behaviors which fire when the value of a Unit Property is
203 changed. You can override a UnitProperty's __set__ method to achieve this
204 in Dejavu. For example:
205 <pre>class DatedProperty(UnitProperty):
206     def __set__(self, unit, value):
207         UnitProperty.__set__(self, unit, value)
208         unit.Date = datetime.date.today()
209         parent = unit.Forum()
210         if parent:
211             parent.Date = unit.Date
212
213 class Topic(Unit):
214     Date = UnitProperty(datetime.date)
215     Content = DatedProperty()
216     ForumID = UnitProperty(int)
217
218 class Forum(Unit):
219     Date = UnitProperty(datetime.date)
220
221 Topic.many_to_one('ForumID', Forum, 'ID')</pre>
222 In this example, whenever topic.Content is set, the <tt>__set__</tt>
223 method will be called and the object's <tt>Date</tt> attribute will
224 be modified. Then, the Topic's parent Forum is looked up and <i>its</i>
225 <tt>Date</tt> is modified.</p>
226
227 <p>As with any trigger system, you need to be careful not to have triggers
228 called out of order. For example, if a user changes both the ForumID and
229 Content properties in a single operation (like a web page submit), the old
230 Forum will be incorrectly modified if the Content property is applied
231 first. I don't have any cool tools built into Dejavu to help you with
232 this, but I'm open to suggestions.</p>
233
234 <h5>TriggerProperty</h5>
235 <p>There is also a <tt class='def'>TriggerProperty</tt> class,
236 which overrides <tt>__set__</tt> for you. If the value in question
237 changes (and the unit has a sandbox), then the
238 <tt class='def'>on_set(unit, oldvalue)</tt> method will be called.
239 Override it in your subclass like this:
240 <pre>
241 class NoteContentProperty(TriggerProperty):
242    
243     def on_set(self, unit, oldvalue):
244         unit.LastModified = datetime.date.today()
245 </pre>
246
247 Note that, if you need to know what the <i>new</i> value is, it's
248 already been set on the unit.</p>
249
250 <a name='registering'><h4>Registration of Unit Classes</h4></a>
251 <p>In addition to defining your Unit class, you must also register that
252 class with your application's <tt>StorageManager</tt>. Each class which
253 you want Dejavu to manage must be passed to
254 <tt class='def'>store.register(cls)</tt>.
255 If you create a module with multiple classes, you can register them all
256 at once with <tt class='def'>store.register_all(globals())</tt>. It will
257 grab any Unit subclasses out of your module's globals() (or any other
258 mapping you pass to <tt>register_all</tt>) and register them. It then
259 returns a list of the classes it found.</p>
260
261 <p>The register and register_all methods also register any Associations
262 you have defined between Units.</p>
263
264 <p>If you're using multiple StorageManagers in a network, you must
265 register classes for each of them. You can inspect which classes have
266 been registered to a given store via <tt class='def'>store.classes</tt>,
267 a set. You shouldn't manipulate this structure on your own; use
268 <tt>register</tt> or <tt>register_all</tt> instead.</p>
269
270 <p>Each <tt>StorageManager</tt> object also manages the associations
271 between Unit classes in its <tt class='def'>associations</tt> attribute,
272 which is a simple, unweighted, undirected graph. Whenever you register
273 a Unit class, the SM will add its associations to this graph. The only
274 other common operation is to call
275 <tt class='def'>associations.shortest_path(start, end)</tt>,
276 to retrieve the chain of associations between two Unit classes.</p>
277
278
279 <a name='synchronizing'><h4>Synchronizing the Model</h4></a>
280
281 <p>Any database code in a general-purpose programming language will
282 eventually have to come to terms with the gap between native code
283 types and native storage types. In most cases for us, this means
284 matching Python types (like int and datetime) to database types
285 (like INT8 and TEXT). Dejavu provides this layer for databases by
286 using a mapping layer between your model code (Unit classes) and
287 the underlying tables and columns. The implementation of that is
288 unimportant (and possibly storage-dependent), but Dejavu needs
289 to know the database types in effect in order to translate data safely.</p>
290
291 <p>When you start your application, you need to call
292 <tt class='def'>store.map_all(conflicts='error')</tt> <i>after</i>
293 you have registered all of your Unit classes (but before you attempt
294 to execute commands on them).</p>
295
296 <p>If your application has created all of its own tables using Dejavu,
297 then there is generally nothing to worry about in terms of the "type gap";
298 Dejavu will default to creating columns of the types it knows best,
299 and you may be able to set the store's <tt>auto_discover</tt> attribute
300 to <tt>False</tt> and reduce application start-up time (Dejavu will use
301 a mock mapping layer in this case, based on your model). But if you are
302 building a Dejavu interface onto an existing database, or if you
303 customize/optimize your database by hand, then you should leave
304 it set to <tt>True</tt> (the default) for safety's sake.</p>
305
306
307 <a name='associations'><h3>Associations between Unit Classes</h3></a>
308 <p>Once you've put together some Unit classes, chances are you're going to
309 want to associate them. Generally, this is accomplished by creating a
310 property in the Unit_B class which stores IDs of Unit_A objects (which
311 might be called <i>foreign keys</i> in a database context).
312 <pre>class Archaeologist(Unit):
313     Height = UnitProperty(float)
314
315 class Biography(Unit):
316     ArchID = UnitProperty(int)
317     PubDate = UnitProperty(datetime.date)</pre>
318 In this example, each <tt>Biography</tt> object will have an <tt>ArchID</tt>
319 attribute, which will equal the <tt>ID</tt> of some <tt>Archaeologist</tt>.
320 In Dejavu terms, we say that there is a <i>near class</i> (with a <i>near
321 key</i>) and a <i>far class</i> (with a <i>far key</i>). Associations in
322 Dejavu are not one-way, so it doesn't matter which class you choose for the
323 "near" one and which for the "far" one.</p>
324
325 <p>You could stop at this point in your design, and simply remember what
326 these keys are and how they relate, and manipulate them accordingly. But
327 Dejavu allows you to explicitly declare these associations:
328 <pre>Archaeologist.one_to_many('ID', Biography, 'ArchID')</pre>
329 You pass in the the near key, the far class, and the far key.
330 There are similar methods for one_to_one and many_to_one. In addition,
331 there is a Unit.associate method which allows you to use your own
332 relationship objects.</p>
333
334 <p>What does an explicit association buy for you? First, you can
335 <a href='managing.html#joins'>join</a> Units without having to remember
336 which keys are related. Second, StorageManagers
337 discover associations and fill the <tt>store.associations</tt> registry, so
338 that smart consumer code (like <a href='managing.html#unitenginerules'>Unit
339 Engine Rules</a>) can automatically follow association paths for you.
340 Third, each Unit class has a private <tt>_associations</tt> attribute,
341 a <tt>dict</tt>. Each Unit involved in in the association gains an entry
342 in that dict: the key is the far class name,
343 and the value is a UnitAssociation instance, a non-data (method) descriptor,
344 with additional nearClass, nearKey, farClass, farKey, and to_many attributes.</p>
345
346 <h4><tt>Unit.add()</tt></h4>
347 <p>Once two classes have been associated, you attach Unit <i>instances</i>
348 to each other by equating their associated properties. That was a
349 mouthful. Here's an example:
350 <pre>>>> evbio = Biography()
351 >>> evbio.ArchID = Eversley.ID
352 </pre>
353 The two unit <i>instances</i> (evbio and Eversley) are now associated
354 (only their <i>classes</i> were before). Keep in mind that many Unit
355 instances need to be memorized in order to obtain an ID.</p>
356
357 <p>Rather than forcing you to remember all of the related classes and keys,
358 Dejavu Units all have an <tt>add</tt> method, which does the same thing:
359 <pre>>>> evbio = Biography()
360 >>> evbio.add(Eversley)
361 </pre>
362 The <tt>add</tt> method works in either direction, so you could just as
363 well write:
364 <pre>>>> evbio = Biography()
365 >>> Eversley.add(evbio)
366 </pre>
367 The <tt>add</tt> method will take any number of unit instances as
368 arguments, and add each one in turn. That is:
369 <pre>
370 >>> evbio1 = Biography()
371 >>> evbio2 = Biography()
372 >>> evbio3 = Biography()
373 >>> Eversley.add(evbio1, evbio2, evbio3)
374 </pre>
375 </p>
376
377 <h4>"Related units" methods</h4>
378 <p>To make querying easier, each of the two Unit classes involved in an
379 association will gain a new
380 "related units" method which simplifies looking up related instances
381 of the other class. The new method for Unit_B will have the name of Unit_A,
382 and vice-versa. In our example:
383 <pre>>>> Archaeologist.Biography
384 &lt;unbound method Archaeologist.related_units>
385
386 >>> Eversley = Archaeologist(Height=6.417)
387 >>> Eversley.Biography
388 &lt;bound method Archaeologist.related_units of &lt;__main__.Archaeologist
389 object at 0x011A1930>>
390
391 >>> bios = Eversley.Biography()
392 >>> bios
393 [&lt;arch.Biography object at 0x01158E10>,
394  &lt;arch.Biography object at 0x0118B350>,
395  &lt;arch.Biography object at 0x0118B170>]
396 >>> evbio1.Archaeologist()
397 &lt;__main__.Archaeologist object at 0x011A1930>
398 </pre>
399 We've only created three Biographies at this point, so we can print the list
400 easily. At the other extreme (when you have hundreds of Biographies to filter),
401 you can pass an optional <tt>Expression</tt> object or keyword arguments
402 to the "related units" method, just like you can with
403 <a href='managing.html#recall'><tt>recall</tt></a>.
404 When you do, the list of associated Units will be filtered accordingly.</p>
405
406 <p>Notice that, because our relationship is one-to-many, <b>the two
407 "related units" methods behave differently</b>. The "one"
408 (Archaeologist) which is retrieving the "many" (Biography) retrieves
409 a list. The "many" retrieving the "one" retrieves a single Unit.
410 When retrieving "to-one", the result will always be a single Unit
411 (or None if there is no matching Unit). When retrieving "to-many",
412 the result will always be a list, (it will be empty if there are
413 no matches).</p>
414
415 <p>Because the "related units" method names are formed automatically, you need
416 to <b>take care not to use the names of Unit classes for your Unit properties</b>.
417 In our example, we used "ArchID" for the name of our "foreign key". If we
418 had used "Archaeologist" instead, we would have had problems; when we
419 associated the classes, the <i>property</i> named "Archaeologist" would
420 have collided with the <i>"related units" method</i> named "Archaeologist".
421 Be careful when naming your properties, and plan for the future. The best
422 approach is probably to end your property name with "ID" every time.</p>
423
424 <p>Unlike some other ORM's, Dejavu doesn't cache far Units within the near
425 Unit. Each time you call the "related units" method, the data is recalled
426 from your Sandbox. It is quite probable that those far Units are still
427 sitting in memory in the Sandbox, but they're not going to persist in
428 the near Unit itself in any way.</p>
429
430 <p>Finally, some of you may want to override the default "related units"
431 methods. Feel free; <tt>Unit.associate</tt> takes two optional arguments,
432 which should be subclasses of the UnitAssociation descriptor. See the
433 source code for more information.</p>
434
435 <h4>Custom Unit Associations</h4>
436
437 <p>Sometimes you need an association between two classes that is more complicated.
438 For example, you might have an Archaeologist object and want to retrieve
439 just their <i>last</i> Biography. Here's an example of how to do this:
440 <pre>class LastBiographyAssociation(dejavu.UnitAssociation):
441     """Unit Association to relate an Archaeologist to their last Biography."""
442    
443     to_many = False
444     register = False
445    
446     def related(self, unit, expr=None, **kwargs):
447         bios = unit.Biography(expr, order=["PubDate DESC"], **kwargs)
448         try:
449             return bios.next()
450         except StopIteration:
451             return None
452
453 descriptor = LastBiographyAssociation(u'ID', Biography, u'ID')
454 descriptor.nearClass = Archaeologist
455 Archaeologist._associations["Last Biography"] = descriptor</pre>
456
457 There are a couple of things to note, here. We are basically doing by
458 hand what the <tt>associate</tt> method does for you automatically, but
459 that method makes <i>two</i> associations (one in each direction), and
460 we're only making one. The <tt class='def'>related(unit, expr, **kw)</tt>
461 method is overridden to do the actual lookup of far units. Because the
462 <tt>to_many</tt> attribute is False, <tt>related</tt> returns a single
463 Unit, or None. Finally, the <tt>register</tt> attribute, when False,
464 keeps the store from registering this association in its graph
465 (see <a href='#registering'>Registration</a>, above).</p>
466
467
468 <a name='schemas'><h3>Managing Schemas</h3></a>
469
470 <h4>Conflicts</h4>
471
472 <p>Dejavu helps you make a <i>model</i> (in Python code) that matches some
473 <i>reality</i> (like an RDBMS, file, or cache) elsewhere. Because both the
474 model and reality can change independently, you'll find <i>conflicts</i>
475 between them from time to time. The most common occurrence of such conflicts
476 is during a call to <tt>map_all</tt>, since it tries to match up your entire
477 model to reality. Similar conflicts arise whenever you ask Dejavu to make
478 changes to reality: add an index, drop storage, or rename a property.</p>
479
480 <p>When conflicts may occur, Dejavu adds a <tt>conflicts</tt> argument to
481 the method arguments. The value you supply for this argument tells Dejavu
482 what to do if a conflict arises:</p>
483
484     <ul>
485     <li><b>error</b>: This is the default value. <tt>MappingError</tt> is
486         raised for the first conflict and the call is aborted.</li>
487     <li><b>warn</b>: StorageWarning is raised (instead of an error) for
488         each issue, and the call is not aborted. This allows you to see all
489         errors at once, without having to stop and fix each one and then
490         execute the call again.</li>
491     <li><b>repair</b>: Each issue will be resolved by changing the database
492         to match the model. Not all calls support this mode for all errors;
493         any which do not support this mode will error instead.</li>
494     <li><b>ignore</b>: Any model conflicts are silently ignored. Use of this
495         mode causes mandelbugs. You have been warned.</li>
496     </ul>
497
498 <h4>Installation</h4>
499
500 <p>Since this procedure typically happens once per deployed application,
501 Dejavu doesn't try to over-engineer it. But the deployer will still have
502 to go through an installation step at some point. Dejavu offers minimal
503 library calls on top of which you can then build installation tools
504 (and upgrade, and uninstall tools).</p>
505
506 <p>For example, a simple install process could look like this:</p>
507
508 <pre>
509 elif cmd == "install":
510     store.log = getlogger(os.path.join(os.getcwd(), localDir, "install.log"))
511     store.logflags = logflags.ERROR + logflags.SQL + logflags.SANDBOX
512    
513     print "Creating databases...",
514     store.create_database()
515     print "ok"
516    
517     print "Creating tables...",
518     store.map_all(conflicts='repair')
519     print "ok"
520    
521     sys.exit(0)
522 </pre>
523
524 <p>In addition to <tt class='def'>create_database(conflicts='error')</tt>,
525 all Storage Managers also have a
526 <tt class='def'>drop_database(conflicts='error')</tt> method.</p>
527
528 <h4>Modifying Storage Structures</h4>
529
530 <p>The <tt>StorageManager</tt> class has some methods to help you make
531 changes to keep storage structures in sync with changes to your Unit
532 classes. For example, let's say that we deploy our
533 Archaeology-Biography application at various libraries around the world.
534 After a year, one of the developers wishes to implement a new reporting
535 feature; however, it would be easiest to build if the Unit Property names
536 could be exposed to the users. Unfortunately, our "ArchID" property on the
537 Biography class isn't very informative. It would be better if we could
538 rename that to "ArchaeologistID":</p>
539
540 <pre>
541 store.rename_property(Biography, "ArchID", "ArchaeologistID")
542 </pre>
543
544 <p>Assuming we've already made the change to our model, the above example
545 renames the property in the persistence layer (the database) using the
546 <tt class='def'>rename_property(cls, oldname, newname, conflicts='error')</tt>
547 method. Additional <tt>StorageManager</tt> methods:</p>
548
549 <p>Unit classes (tables):</p>
550 <ul>
551 <li><tt class='def'>create_storage(cls, conflicts='error')</tt></li>
552 <li><tt class='def'>has_storage(cls)</tt></li>
553 <li><tt class='def'>drop_storage(cls, conflicts='error')</tt></li>
554 </ul>
555
556 <p>Unit properties (columns):</p>
557 <ul>
558 <li><tt class='def'>add_property(cls, name, conflicts='error')</tt></li>
559 <li><tt class='def'>has_property(cls, name)</tt></li>
560 <li><tt class='def'>drop_property(cls, name, conflicts='error')</tt></li>
561 </ul>
562
563 <p>Unit property (column) indices:</p>
564 <ul>
565 <li><tt class='def'>add_index(cls, name, conflicts='error')</tt></li>
566 <li><tt class='def'>has_index(cls, name)</tt></li>
567 <li><tt class='def'>drop_index(cls, name, conflicts='error')</tt></li>
568 </ul>
569
570
571 <h4>Upgrading: Schema Objects</h4>
572
573 <p>The <tt>Schema</tt> class helps you manage changes to your Dejavu model
574 throughout its lifetime. Taking our <tt>rename_property</tt> example from
575 above, we can rewrite it in a Schema obejcts like this:</p>
576
577 <pre>class ArchBioSchema(dejavu.Schema):
578    
579     guid = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
580     latest = 2
581    
582     def upgrade_to_2(self):
583         self.store.rename_property(Biography, "ArchID", "ArchaeologistID")
584
585 abs = ArchBioSchema(store)
586 abs.upgrade()
587 </pre>
588
589 <p>The example declares this change to be "version 2" of our schema.
590 If you examine the base Schema class, you will see that it already has an
591 <tt>upgrade_to_0</tt> method. The "zeroth" upgrade makes no schema changes;
592 it merely marks all deployed databases with "version 0". I skipped version
593 1 in the example, just in case I need some setup code in the future ;).</p>
594
595 <p>If you call <tt class='def'>schema.upgrade(version)</tt> with a
596 version argument, then your deployment will be upgraded to that version.
597 If no argument is given, the installation will be upgraded to
598 <tt>schema.latest</tt>. You can even skip steps (i.e. remove methods
599 for broken steps) if it comes to that.</p>
600
601 <p>Each Schema also has a <tt class='def'>stage</tt> attribute.
602 While an upgrade is in process, this value will be an int, the same number
603 as that of the upgrade method. That is, while upgrade_to_2 is running,
604 <tt>stage</tt> will be 2. If no upgrade method is running, <tt>stage</tt>
605 will be None.</p>
606
607 <p>After you run <tt>upgrade</tt>, you can call the
608 <tt class='def'>assert_storage</tt> method of the Schema object
609 to tell Dejavu to create storage (tables in your database)
610 for all the Unit classes registered in your store.
611 If storage already exists for a given class, it is skipped.</p>
612
613 <p><b>Please note:</b> the installed version defaults to "latest". This
614 allows new installs to skip all the upgrade steps, and just use the latest
615 class definitions when they call <tt>assert_storage</tt>. However, it means
616 that if you deploy your apps for a while without a Schema, and then
617 introduce one later, you must manually decrement DeployedVersion from
618 "latest" to the actual deployed version *before* running your app for the
619 first time (or things will break due to the difference between the latest
620 and deployed schema).</p>
621
622
623 <h4>Versions: The DeployedVersion Unit</h4>
624
625 <p>The <tt>Schema</tt> class uses a magic table in the database to keep
626 track of each deployment's schema version. The Unit class is called
627 "DeployedVersion", and it has ID and Version attributes.</p>
628
629 <p>The ID attribute will be set to whatever your <tt>Schema.guid</tt>
630 is. It's a simple way to isolate multiple installed Dejavu applications.
631 A given application should use the same guid throughout its lifetime.
632 I used <tt>sha.new().hexdigest()</tt> to generate the example. Feel free
633 to use sha.new, a guid generator, a descriptive name, or whatever you
634 like.</p>
635
636
637 <a name='autoclass'><h3>Automatic Unit Classes</h3></a>
638
639 <p>When you create your first Dejavu model, you might be forming it
640 to match some existing database schema. If so, Dejavu has a
641 <tt>Modeler</tt> tool to help you inside <tt>dejavu.storage.db</tt>.
642 </p>
643
644 <p>The <tt class='def'>make_class(tablename, newclassname=None)</tt>
645 method finds an existing Table by name and returns a subclass
646 of Unit which models that table. By default, the new class
647 will have the same name as the database table; supply the
648 'newclassname' argument to use a different name (for example,
649 to capitalize the class name).</p>
650
651 <pre>
652 >>> sm = storage.resolve('mysql',
653                          {"host": "localhost", "db": "existing_db",
654                           "user": "root", "passwd": "xxxx",
655                           })
656 >>> from dejavu.storage import db
657 >>> modeler = db.Modeler(s.db)
658 >>> Zoo = modeler.make_class("zoo", "Zoo")
659 >>> Zoo
660 &lt;class 'dejavu.storage.db.Zoo'>
661 >>> Zoo.properties
662 ['id', 'name', 'admission', 'founded', 'lastescape', 'opens']
663 </pre>
664
665 <p>The <tt class='def'>make_source(tablename, newclassname=None)</tt>
666 method does the same thing as <tt>make_class</tt>, but returns a string
667 that contains valid Python code to generate the requested Unit class:</p>
668
669 <pre>
670 >>> print modeler.make_source("exhibit", "Exhibit")
671 class Exhibit(Unit):
672     pettingallowed = UnitProperty(bool)
673     animals = UnitProperty(str)
674     name = UnitProperty(str)
675     zooid = UnitProperty(int)
676     acreage = UnitProperty(decimal.Decimal)
677     creators = UnitProperty(str)
678     # Remove the default 'ID' property.
679     ID = None
680     identifiers = ('name', 'zooid')
681     sequencer = UnitSequencer()
682 </pre>
683
684 <p>Finally, you can perform this sort of modeling on <i>all</i> tables
685 in a database at once with the <tt class='def'>all_classes()</tt> and
686 <tt class='def'>all_source()</tt> methods of the Modeler. These simply
687 iterate over all of the known tables and return the results in a list
688 instead of a single value.</p>
689
690 <hr />
691
692 </body>
693 </html>
Note: See TracBrowser for help on using the browser.