| 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 |
<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 |
(<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 >= 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 >= 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 >= 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 |
<unbound method Archaeologist.related_units> |
|---|
| 385 |
|
|---|
| 386 |
>>> Eversley = Archaeologist(Height=6.417) |
|---|
| 387 |
>>> Eversley.Biography |
|---|
| 388 |
<bound method Archaeologist.related_units of <__main__.Archaeologist |
|---|
| 389 |
object at 0x011A1930>> |
|---|
| 390 |
|
|---|
| 391 |
>>> bios = Eversley.Biography() |
|---|
| 392 |
>>> bios |
|---|
| 393 |
[<arch.Biography object at 0x01158E10>, |
|---|
| 394 |
<arch.Biography object at 0x0118B350>, |
|---|
| 395 |
<arch.Biography object at 0x0118B170>] |
|---|
| 396 |
>>> evbio1.Archaeologist() |
|---|
| 397 |
<__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 |
<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> |
|---|