Changeset 38
- Timestamp:
- 12/15/04 22:50:08
- Files:
-
- trunk/__init__.py (modified) (14 diffs)
- trunk/doc/api.html (deleted)
- trunk/doc/dejavu.css (modified) (1 diff)
- trunk/doc/framework.html (modified) (4 diffs)
- trunk/doc/index.html (modified) (3 diffs)
- trunk/doc/intro.html (modified) (7 diffs)
- trunk/doc/modeling.html (modified) (14 diffs)
- trunk/doc/storage.html (modified) (4 diffs)
- trunk/engines.py (modified) (4 diffs)
- trunk/storage/__init__.py (modified) (2 diffs)
- trunk/storage/test_storeado.py (modified) (1 diff)
- trunk/test_dejavu.py (modified) (2 diffs)
- trunk/zoo.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/__init__.py
r37 r38 81 81 82 82 class UnitSequencerNull(object): 83 """A null sequencer for Unit IDs. Sequencing will error. 83 """UnitSequencerNull(type=unicode). 84 A null sequencer for Unit IDs. Sequencing will error. 84 85 85 86 In many cases, ID values simply have no algorithmic sequence; … … 100 101 101 102 class UnitSequencerInteger(object): 102 """A sequencer for Unit IDs, where id[i+1] == id[i] + 1.""" 103 """UnitSequencerInteger(type=int, initial=1) 104 A sequencer for Unit IDs, where id[i+1] == id[i] + 1.""" 103 105 104 106 def __init__(self, type=int, initial=1): … … 115 117 116 118 class UnitSequencerUnicode(object): 117 """A sequencer for Unit IDs, where e.g. next(['abc']) == 'abd'.""" 119 """UnitSequencerUnicode(type=unicode, width=6, 120 range="abcdefghijklmnopqrstuvwxyz") 121 A sequencer for Unit IDs, where e.g. next(['abc']) == 'abd'.""" 118 122 119 123 def __init__(self, type=unicode, width=6, … … 158 162 159 163 class UnitProperty(object): 160 """Data descriptor for Unit data which will persist in storage. 164 """UnitProperty(type=unicode, index=False, hints={}, key=None) 165 Data descriptor for Unit data which will persist in storage. 161 166 162 167 pre, post: Override these with functions to provide custom behaviors … … 180 185 181 186 def __init__(self, type=unicode, index=False, hints={}, key=None): 182 self.type = type183 187 if type.__name__ == 'FixedPoint': 184 188 # fixedpoint.Fixedpoint can't be pickled because it … … 186 190 _define_fixedpoint_states() 187 191 192 self.type = type 188 193 self.index = index 189 194 self.hints = hints … … 249 254 250 255 class Unit(object): 251 """A generic object, the building-block of Dejavu. 252 253 These are purposefully lightweight, relying on Sandboxes to cache 254 them, which in turn rely on Storage Managers to load and save them. 256 """Unit(**kwarg properties). A generic, persistent object. 257 258 Units are the building-block of Dejavu. They are purposefully lightweight, 259 relying on Sandboxes to cache them, which in turn rely on Storage Managers 260 to load and save them. 255 261 256 262 They maintain their own "schema" via UnitProperty objects, so that the … … 387 393 self._properties, self._initial_property_hash = state 388 394 395 396 # Associations # 397 398 def first(self, farClass, **kwargs): 399 """Return the first associated farClass Unit or None. 400 401 Passes additional kwargs to sandbox.unit(). 402 """ 403 try: 404 key, farKey = self.__class__._associations[farClass] 405 except KeyError: 406 raise AssociationError("'%s' is not associated with '%s'" 407 % (self.__class__, farClass)) 408 409 value = getattr(self, key) 410 if value is None: 411 return None 412 413 # kwargs won't take unicode keys 414 kwargs[str(farKey)] = value 415 return self.sandbox.unit(farClass, **kwargs) 416 389 417 def add(self, unit): 390 418 """add(unit) -> Auto-create a relationship between self and unit.""" … … 407 435 408 436 437 def relation_factory(key, farClass, farKey): 438 """Produce a new recaller method for a Unit subclass.""" 439 def related_units(self, expr=None): 440 value = getattr(self, key) 441 if value is None: 442 return iter([]) 443 444 # kwargs won't take unicode keys 445 f = logic.filter(**{str(farKey): value}) 446 if expr is not None: 447 f += expr 448 return self.sandbox.recall(farClass, f) 449 450 related_units.__doc__ = ( 451 """Iterator over '%(farname)s' Units whose %(farkey)s matches self.%(key)s. 452 If self.%(key)s is None, no Units will be recalled.""" 453 % {'farname': farClass.__name__, 454 'farkey': farKey, 455 'key': key, 456 }) 457 return related_units 458 459 def associate(cls, key, farClass, farKey, nearFactory=None, farFactory=None): 460 """Associate one Unit class with another by relating attributes. 461 462 cls, key: The 'near' class and its key. 463 farClass, farKey: the 'far' class and its key. 464 465 Far Units will be recalled if their farKey matches cls.key. 466 However, if cls.key is empty or None, no Units will be recalled. 467 """ 468 469 # Add a method to cls which retrieves farClass Units 470 if nearFactory is None: 471 nearFactory = relation_factory 472 func = nearFactory(key, farClass, farKey) 473 setattr(cls, farClass.__name__, func) 474 475 # Add the farClass to the association dictionary of cls. 476 cls._associations[farClass] = (key, farKey) 477 478 # Add a method to farClass which retrieves cls Units 479 if farFactory is None: 480 farFactory = relation_factory 481 func = farFactory(farKey, cls, key) 482 setattr(farClass, cls.__name__, func) 483 484 # Add the cls to the association dictionary of farClass. 485 farClass._associations[cls] = (farKey, key) 486 487 409 488 ########################################################################### 410 489 ## ## … … 415 494 416 495 class Arena(object): 417 """A namespace/workspace for a Dejavu application."""496 """Arena(). A namespace/workspace for a Dejavu application.""" 418 497 419 498 def __init__(self): … … 485 564 # We left cls == None in _load(). Set it now. 486 565 self.roster.facets['cls'][row] = cls 566 567 # Register any association(s) in an undirected graph. 568 for farClass in cls._associations: 569 self.associations.connect(cls, farClass) 570 571 def register_all(self, globals): 572 for obj in globals.itervalues(): 573 if isinstance(obj, type) and issubclass(obj, Unit): 574 self.register(obj) 487 575 488 576 def class_by_name(self, classname): … … 494 582 s = self.defaultStore 495 583 if s is None: 496 raise DejavuError("No storage for class '%s'" % cls.__name__) 584 raise DejavuError("No storage defined for class '%s'" % 585 cls.__name__) 497 586 return s 498 587 499 588 def create_storage(self, cls): 500 589 self.storage(cls).create_storage(cls) 501 502 def associate(self, cls, key, farClass, farKey,503 nearFactory=None, farFactory=None):504 """Associate one Unit class with another by relating attributes.505 506 cls, key: The 'near' class and its key.507 farClass, farKey: the 'far' class and its key.508 509 Far Units will be recalled if their farKey matches cls.key.510 However, if cls.key is empty or None, no Units will be recalled.511 """512 513 # Disallow overwriting of existing attributes.514 if hasattr(cls, farClass.__name__):515 raise AssociationError(u"%s already has an attribute named '%s'."516 % (cls, farClass.__name__))517 if hasattr(farClass, cls.__name__):518 raise AssociationError(u"%s already has an attribute named '%s'."519 % (farClass, cls.__name__))520 521 # Assert that both classes are registered.522 self.register(cls)523 self.register(farClass)524 525 # Add a method to cls which retrieves farClass synapses526 if nearFactory is None:527 nearFactory = _synapses_func528 func = nearFactory(key, farClass, farKey)529 setattr(cls, farClass.__name__, func)530 cls._associations[farClass] = (key, farKey)531 532 # Add a method to farClass which retrieves cls synapses533 if farFactory is None:534 farFactory = _synapses_func535 func = farFactory(farKey, cls, key)536 setattr(farClass, cls.__name__, func)537 farClass._associations[cls] = (farKey, key)538 539 # Register the association(s) in an undirected graph.540 self.associations.connect(cls, farClass)541 590 542 591 … … 544 593 # process. Otherwise, you should create your own instance per application. 545 594 dejavuarena = Arena() 546 547 def _synapses_func(key, farClass, farKey):548 """Produce a new synapses() function to be bound to a Unit class."""549 def synapses(self, expr=None):550 """Recall associated '%(farname)s' Units.551 552 %(farname)s Units will be recalled if their %(farkey)s matches553 self.%(key)s. However, if self.%(key)s is None,554 no Units will be recalled.555 """ % {'farname': farClass.__name__,556 'farkey': farKey,557 'key': key,558 }559 value = getattr(self, key)560 if value is None:561 return iter([])562 563 # kwargs won't take unicode keys564 f = logic.filter(**{str(farKey): value})565 if expr is not None:566 f += expr567 return self.sandbox.recall(farClass, f)568 return synapses569 595 570 596 … … 577 603 578 604 class Sandbox(object): 579 """ Data sandbox for Dejavu arenas.605 """Sandbox(arena). Data sandbox for Dejavu arenas. 580 606 581 607 Each consumer (that is, each UI process) maintains a Sandbox for trunk/doc/dejavu.css
r37 r38 64 64 } 65 65 66 pre { 67 margin-left: 3em; 68 } 69 66 70 /*, table { border: 2px dotted red } trunk/doc/framework.html
r37 r38 37 37 creating a custom Storage Manager, whether for a new backend, or a custom 38 38 middleware component. Storage Managers must conform to a simple interface 39 for creating, destroying, and recalling Units. Beyond that, they are free 40 to do whatever they like. 41 </p> 39 for creating, destroying, and recalling Units. They are free to implement 40 that functionality however they like.</p> 42 41 43 42 <p>As you can see in the code, the <tt>storage.StorageManager</tt> base … … 54 53 not need to be fully populated. If the Unit has an ID when 55 54 passed to <tt>reserve</tt>, use it. If not, supply it using 56 the class' UnitSequencer. You are not required to perist57 any Unit attributes other than UnitProperties.</li>55 the class' UnitSequencer. If your database provides equivalent 56 sequencing to dejavu Sequencers, feel free to use it.</li> 58 57 <li><tt>save(self, unit, forceSave=False):</tt> Required. Take the 59 supplied Unit instance and persist its properties to storage.</li> 58 supplied Unit instance and persist its properties to storage. 59 You are not required to persist any Unit attributes other than 60 UnitProperties.</li> 60 61 <li><tt>destroy(self, unit):</tt> Required. Remove the Unit's data 61 62 from storage permanently.</li> … … 217 218 218 219 <h4>Other Serialization Mechanisms</h4> 219 <h5>shelve</h5>220 <p>Extremely simple implementation. Everything is pickled. Querying will221 be slow--every Unit is sucked in one-by-one and tested in pure Python222 using <tt>Expression.evaluate()</tt>. But for many applications, you don't223 need heavyweight query tools; for example, an online forum may only need224 topic content looked up by ID. Or system tables that only get read at225 startup might benefit.</p>226 227 220 <h5>sockets</h5> 228 221 <p>There's a <tt>sockets</tt> module in the <tt>storage</tt> package. … … 231 224 with a third-party database, which couldn't handle web-traffic threading 232 225 models. Here's a snippet of how to use it (from that app): 233 <pre> def query(self, cmd, unitType='', data=None):234 if isinstance(data, dejavu.Unit):235 data = stream(data)236 elif data is None:237 data = ''238 else:239 data = pickle.dumps(data)240 response = self.socket.query(":".join((cmd, unitType, data)))241 return response226 <pre>def query(self, cmd, unitType='', data=None): 227 if isinstance(data, dejavu.Unit): 228 data = stream(data) 229 elif data is None: 230 data = '' 231 else: 232 data = pickle.dumps(data) 233 response = self.socket.query(":".join((cmd, unitType, data))) 234 return response 242 235 </pre> 243 236 </p> trunk/doc/index.html
r37 r38 28 28 <ul> 29 29 <li>UnitProperty</li> 30 <li>Unit ID's</li> 30 31 <li>Creating and Populating Properties</li> 31 32 <li>Unit Properties are First-Class Objects</li> 32 33 <li>Triggers</li> 33 <li>Unit ID's</li>34 34 <li>Registration of Unit Classes</li> 35 35 </ul> … … 56 56 </ul> 57 57 </li> 58 <li>Associations between Unit Classes</li> 58 <li>Associations between Unit Classes 59 <ul> 60 <li><tt>related_units</tt> methods</li> 61 <li><tt>Unit.first()</tt></li> 62 </ul> 63 </li> 59 64 <li>Unit Engines 60 65 <ul> … … 85 90 <li>Microsoft SQL Server</li> 86 91 <li>ODBC</li> 92 <li>Shelve</li> 87 93 </ul> 88 94 </li> trunk/doc/intro.html
r37 r38 72 72 from dejavu.storage.storeado import StorageManagerADO_MSAccess as SM 73 73 74 # Set up a global Arena object.75 arena = dejavu.Arena()76 conf = {u'Connect':77 r"PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=C:\zookeeper.mdb;"}78 arena.add_store('main', SM("mySM", arena, conf))79 80 74 81 75 class Zoo(dejavu.Unit): … … 91 85 "ZooID": int, 92 86 }) 93 arena.associate(Zoo, 'ID', Animal, 'ZooID')</pre> 87 dejavu.associate(Zoo, 'ID', Animal, 'ZooID') 88 89 # Set up a global Arena object. 90 arena = dejavu.Arena() 91 conf = {u'Connect': 92 r"PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=C:\zookeeper.mdb;"} 93 arena.add_store('main', SM("mySM", arena, conf)) 94 arena.register_all(globals())</pre> 94 95 95 96 <p>The above creates the model/schema for the zookeeper application. … … 101 102 methods of setting Unit properties. Each class also inherits 102 103 an 'ID' property (an int) from <tt>dejavu.Unit</tt>.</li> 104 <li>The association between the Zoo class and the Animal class.</li> 103 105 <li>The setup of a dejavu <tt>Arena</tt> object, including a Storage 104 106 Manager which uses a Microsoft Access (Jet) database.</li> 105 <li>The association between the Zoo class and the Animal class.</li>106 107 </ol> 107 108 </p> … … 174 175 175 176 <p>You can obtain Dejavu from its Subversion repository at 176 <tt>svn://casadeamor.com/dejavu/trunk</tt>. Dejavu expects to be 177 installed in <tt>site-packages/dejavu</tt>.</p> 177 <tt>svn://casadeamor.com/dejavu/trunk</tt>. Dejavu uses no relative imports, 178 and therefore expects to be installed in <tt>site-packages/dejavu</tt> 179 or some other root python path.</p> 178 180 179 181 <p>Dejavu depends upon two additional libraries, <tt>recur.py</tt> and 180 <tt>xray.py</tt>, which are available at <tt>svn://casadeamor.com/misc</tt>. 181 You need to place these two modules in your site-packages directory.</p> 182 <tt>xray.py</tt>, which are available at <tt>svn://casadeamor.com/misc/trunk</tt>. 183 You need to place these two modules in your python path, usually 184 site-packages.</p> 182 185 183 186 <p>Dejavu was built using Python 2.3.2. You should probably use … … 192 195 <h3>Compared To Other Database Wrappers</h3> 193 196 <h4>SQLObject</h4> 194 <p>See http://blog.colorstudy.com/ianb/weblog/2004/09/06.html#P154 197 <p>No matter what project I start on, odds are I'll discover that Ian 198 Bicking has already done the same thing, usually better. 199 <br />See http://blog.ianbicking.org/another-less-sleepy-alternative-to-hibernate.html 195 200 <br />Which was a reply to Ruby's ActiveRecord: 196 201 http://www.loudthinking.com/arc/000297.html … … 235 240 bookID = UnitProperty(int) 236 241 242 dejavu.associate(Book, 'publisher', Publisher, 'ID') 243 dejavu.associate(Authorship, 'bookID', Book, 'ID') 244 dejavu.associate(Authorship, 'authorID', Author, 'ID') 245 237 246 arena = Arena() 238 arena.associate(Book, 'publisher', Publisher, 'ID') 239 arena.associate(Authorship, 'bookID', Book, 'ID') 240 arena.associate(Authorship, 'authorID', Author, 'ID') 247 arena.register_all(globals()) 241 248 </pre> 242 249 … … 276 283 277 284 ppython.publisher = oreilly.ID 278 print ppython. Publisher().next().name # output: "O'Reilly"285 print ppython.first(Publisher).name # output: "O'Reilly" 279 286 280 287 print len([x for x in oreilly.Book()]) # output: 1 281 288 282 print 'Hi,', oreilly. Book().next().author_names() # output: "Hi, Mark Lutz"289 print 'Hi,', oreilly.first(Book).author_names() # output: "Hi, Mark Lutz" 283 290 </pre> 284 291 </p> trunk/doc/modeling.html
r37 r38 14 14 15 15 <h3>Units</h3> 16 <p>When constructing a Domain Model for your application, you need 17 to have a distinction between data that will be persisted and data that 18 will not. At the most general level, you might say that some entire 19 <i>objects</i> need to be persisted. By registering a subclass of 20 <tt>dejavu.Unit</tt>, you specify a set of objects (instances of your 21 class) which will be persisted.</p> 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> 22 20 23 21 <p>Before you can register your Unit class, you must create it: … … 46 44 This adds three persistent attributes to our <tt>Printer</tt> objects, 47 45 each with a different datatype. In addition, every subclass of <tt>Unit</tt> 48 inherits an 'ID' attribute, an int.</p>46 inherits an 'ID' property, an int.</p> 49 47 50 48 <p>When you get and set <tt>UnitProperty</tt> attributes, they behave just … … 56 54 However, you will notice right away that the int value we provided has been 57 55 coerced to a float behind the scenes. This is because we specified the PPM 58 attribute as a 'float' type when we created it. Unit Properties are59 restricted to the types which you specify. The only other valid value 60 for a Unit Property is None; any Property may be None at any time, and 61 in fact, all Properties are None until you assign values to them: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: 62 60 <pre>>>> p.ColorCopies is None 63 61 True</pre></p> 62 63 <h4>Unit ID's</h4> 64 <p>The <tt>Unit</tt> base class possesses a single Unit Property, an int 65 named 'ID'. If you wish to use ID's of a different type, simply override 66 the ID attribute in your subclass: 67 <pre>class Printer(Unit): 68 ID = UnitProperty(unicode)</pre> 69 Every Unit must possess an ID property. This ensures that each Unit within 70 the system is unique.</p> 64 71 65 72 <h4>Creating and Populating Properties</h4> … … 124 131 where 0 implies no limit.</p> 125 132 133 <p>When you define a UnitProperty instance, you can pass in these extra 134 attributes. The signature for UnitProperty is <tt>(type=unicode, 135 index=False, hints={}, key=None)</tt>. Supply any, all, or none of them 136 as needed.</p> 137 126 138 <h4>Triggers</h4> 127 139 <p>In addition, each UnitProperty has a <tt>pre</tt> and <tt>post</tt> … … 133 145 def post(self, unit, value): 134 146 unit.Date = datetime.datetime.now().replace(microsecond=0) 147 parent = unit.first(Forum) 148 if parent: 149 parent.Date = unit.Date 135 150 136 151 class Topic(Unit): 137 152 Date = UnitProperty(datetime.date) 138 Content = DatedProperty()</pre> 153 Content = DatedProperty() 154 ForumID = UnitProperty(int) 155 156 class Forum(Unit): 157 Date = UnitProperty(datetime.date) 158 159 associate(Topic, 'ForumID', Forum, 'ID')</pre> 139 160 In this example, whenever Topic().Content is set, the <tt>post</tt> 140 161 method will be called and the object's <tt>Date</tt> attribute will 141 be modified.</p> 142 143 <h4>Unit ID's</h4> 144 <p>The <tt>Unit</tt> base class possesses a single Unit Property, an int 145 named 'ID'. If you wish to use ID's of a different type, simply override 146 the ID attribute in your subclass: 147 <pre>class Printer(Unit): 148 ID = UnitProperty(unicode)</pre> 149 Every Unit must possess an ID property. This ensures that each Unit within 150 the system is unique.</p> 162 be modified. Then, the Topic's parent Forum is looked up and <i>its</i> 163 <tt>Date</tt> is modified.</p> 164 165 <p>As with any trigger system, you need to be careful not to have triggers 166 called out of order. For example, if a user changes both the ForumID and 167 Content properties in a single operation (like a web page submit), the old 168 Forum will be incorrectly modified if the Content property is applied 169 first. I don't have any cool tools built into Dejavu to help you with 170 this, but I'm open to suggestions.</p> 151 171 152 172 <h4>Registration of Unit Classes</h4> … … 154 174 class with your application's <tt>Arena</tt> object. Each class which 155 175 you want Dejavu to manage must be passed to <tt>Arena.register(cls)</tt>. 156 </p> 176 If you create a module with multiple classes, you can register them all 177 at once with <tt>Arena.register_all(globals())</tt>. It will grab any 178 Unit subclasses out of your module's globals() (or any other mapping 179 you pass to <tt>register_all</tt>) and register them.</p> 157 180 158 181 <h3>Sandboxes</h3> … … 294 317 <h4>Flushing Sandboxes</h4> 295 318 <p>When the client connection has closed, you should <i>flush</i> the 296 Sandbox caches. In general, a single call to <tt>flush_all()</tt> will do 297 the trick. Notice that flushing calls <tt>repress()</tt> for each Unit in 298 the Sandbox, and any <tt>on_repress()</tt> triggers will be executed.</p> 319 Sandbox caches. In general, a single call to <tt>Sandbox().flush_all()</tt> 320 will do the trick. Notice that flushing calls <tt>repress()</tt> for each 321 Unit in the Sandbox, and any <tt>on_repress()</tt> triggers will be 322 executed.</p> 299 323 300 324 … … 317 341 a given class. In Dejavu, you filter the set according to the UnitProperty 318 342 attributes for each object. Naturally, there must be a way to express 319 the filter you intend. Dejavu actually provides three ways: Expressions, 343 the filter you intend. Dejavu actually provides three ways, all in the 344 <tt>dejavu.logic</tt> module: <tt>Expression</tt>, 320 345 <tt>filter</tt>, and <tt>comparison</tt>.</p> 321 346 … … 336 361 always be bound to a Unit instance. In the example above, it's named 'x', 337 362 but you can use any name you like. Using lambdas as a base means that we 338 can simply call Expression.func(Unit), and receive a boolean value339 indicating whether our Unit "passes the test". Attribute lookups on our 340 'x' object will apply to Unit Properties for that Unit object.341 That is, <tt>x.Date</tt> becomes <tt> Unit.Date</tt>.</p>363 can simply call <tt>Expression.evaluate(unit)</tt>, and receive a boolean 364 value indicating whether our Unit "passes the test". Attribute lookups on 365 our 'x' object will apply to Unit Properties for that Unit object. 366 That is, <tt>x.Date</tt> becomes <tt>unit.Date</tt>.</p> 342 367 343 368 <h4>Early binding</h4> … … 547 572 <p>You could stop at this point in your design, and simply remember what 548 573 these keys are and how they relate, and manipulate them accordingly. But 549 Dejavu allows you to <i>register</i> these associations explicitly in your 550 <tt>Arena</tt>: 551 <pre>myArena.associate(Archaeologist, 'ID', Biography, 'ArchID')</pre> 574 Dejavu allows you to explicitly declare these associations: 575 <pre>dejavu.associate(Archaeologist, 'ID', Biography, 'ArchID')</pre> 552 576 You pass in the near class, the near key, the far class, and the far key. 553 577 </p> 554 578 555 <p>What does an explicit association buy for you? First, the <tt>associate</tt> 556 call adds an entry in the <tt>Arena.associations</tt> registry, so that 557 smart consumer code (like Unit Engine Rules, below) can automatically 558 follow association paths for you. Second, each Unit class has a private 559 <tt>_associations</tt> attribute, a <tt>dict</tt>. Each Unit involved 560 in the association gains an entry in that dict: the key is the far class 561 itself (not the class name), and the value is a tuple of (far key, near key). 562 Third, <tt>associate()</tt> can be used to register your Unit classes in 563 the Arena's <tt>roster</tt>; you don't have to call <tt>register</tt> for 564 either class if you call <tt>associate</tt> (see <u>The Arena Object</u>, 565 below).</p> 566 567 <p>In addition, each of the Unit classes will gain a new <i>synapse</i> 568 method which simplifies looking up related instances of the other class. 569 The new method for Unit_B will have the name of Unit_A, and vice-versa. 570 In our example: 579 <p>What does an explicit association buy for you? First, Arenas discover them 580 and fill the <tt>Arena.associations</tt> registry, so that smart consumer 581 code (like Unit Engine Rules, below) can automatically follow association 582 paths for you. Second, each Unit class has a private <tt>_associations</tt> 583 attribute, a <tt>dict</tt>. Each Unit involved in in the association gains 584 an entry in that dict: the key is the far class itself (not the class name), 585 and the value is a tuple of (near key, far key).</p> 586 587 <h4><tt>related_units</tt> methods</h4> 588 <p>In addition, each of the two Unit classes will gain a new 589 <i>related_units</i> method which simplifies looking up related instances 590 of the other class. The new method for Unit_B will have the name of Unit_A, 591 and vice-versa. In our example: 571 592 <pre>>>> Archaeologist.Biography 572 <unbound method Archaeologist. synapses>593 <unbound method Archaeologist.related_units> 573 594 >>> Eversley = Archaeologist(Height=(6.417)) 574 595 >>> Eversley.Biography 575 <bound method Archaeologist. synapses of <__main__.Archaeologist596 <bound method Archaeologist.related_units of <__main__.Archaeologist 576 597 object at 0x011A1930>> 577 598 >>> bios = Eversley.Biography() … … 584 605 which is why we get an empty iterator at this point. At the other extreme 585 606 (when you have hundreds of Biographies to filter), you can pass an optional 586 <tt>Expression</tt> object to the synapse method. When you do, the list of587 associated Units will be filtered accordingly.</p>588 589 <p>Because the synapsemethod names are formed automatically, you need607 <tt>Expression</tt> object to the related_units method. When you do, the 608 list of associated Units will be filtered accordingly.</p> 609 610 <p>Because the related_units method names are formed automatically, you need 590 611 to take care not to use the names of Unit classes for your Unit properties. 591 612 In our example, we used "ArchID" for the name of our "foreign key". 592 613 If we had used "Archaeologist" instead, we would have had problems; 593 614 when we associated the classes, the <i>property</i> named "Archaeologist" 594 would have collided with the <i>synapse method</i> named "Archaeologist". 595 Be careful when naming your properties, and plan for the future.</p> 596 597 <p>Unlike some other ORM's, Dejavu doesn't cache far Units within 598 the near Unit. Each time you call the synapse method, the data is recalled 615 would have collided with the <i>related_units method</i> named 616 "Archaeologist". Be careful when naming your properties, and plan for the 617 future.</p> 618 619 <p>Unlike some other ORM's, Dejavu doesn't cache far Units within the near 620 Unit. Each time you call the related_units method, the data is recalled 599 621 from your Sandbox. It is quite probable that those far Units are still 600 622 sitting in memory in the Sandbox, but they're not going to persist in 601 623 the near Unit itself in any way.</p> 602 624 603 <p>Finally, some of you may want to override the default synapse methods.604 Feel free; <tt>Arena.associate</tt> takes two optional arguments, which605 should be callables that return the new function(s). See the source code606 of <tt>Arena</tt> and the private method <tt>dejavu._synapses_func</tt>625 <p>Finally, some of you may want to override the default related_units 626 methods. Feel free; <tt>associate</tt> takes two optional arguments, which 627 should be callables that create and return the new method(s). See the source 628 code of <tt>dejavu</tt> and the method <tt>dejavu.relation_factory</tt> 607 629 for more information.</p> 608 630 631 <h4><tt>Unit.first()</tt></h4> 632 <p>Associations also enable the <tt>first</tt> method of Units. It's an 633 easy way to get a single related unit. Call it with a far Class and, 634 optionally, keyword arguments. The method will look up the related 635 properties and call sandbox.unit() for you, returning either the first 636 such far Unit or None if not found.</p> 609 637 610 638 <h3>Unit Engines</h3> … … 752 780 a <tt>SetID</tt>, and an <tt>Operand</tt>. Here's an example ruleset:</p> 753 781 <table> 754 <tr><th> Operation</th><th>SetID</th><th>Operand</th></tr>755 <tr><td> CREATE</td><td>1</td><td>Invoice</td></tr>756 <tr><td> FILTER</td><td>1</td><td>(Expression)</td></tr>757 <tr><td> CREATE</td><td>2</td><td>Inventory</td></tr>758 <tr><td> FILTER</td><td>2</td><td>(Expression)</td></tr>759 <tr><td> TRANSFORM</td><td>2</td><td>Invoice</td></tr>760 <tr><td> DIFFERENCE</td><td>1</td><td>2</td></tr>761 <tr><td> RETURN</td><td>1</td><td></td></tr>782 <tr><th>Sequence</th><th>Operation</th><th>SetID</th><th>Operand</th></tr> 783 <tr><td>1</td><td>CREATE</td><td>1</td><td>Invoice</td></tr> 784 <tr><td>2</td><td>FILTER</td><td>1</td><td>(Expression)</td></tr> 785 <tr><td>3</td><td>CREATE</td><td>2</td><td>Inventory</td></tr> 786 <tr><td>4</td><td>FILTER</td><td>2</td><td>(Expression)</td></tr> 787 <tr><td>5</td><td>TRANSFORM</td><td>2</td><td>Invoice</td></tr> 788 <tr><td>6</td><td>DIFFERENCE</td><td>1</td><td>2</td></tr> 789 <tr><td>7</td><td>RETURN</td><td>1</td><td></td></tr> 762 790 </table> 763 791 … … 766 794 of Units. In most cases, you will then FILTER that set. If you simply 767 795 created a set and then returned it, it would contain all Units of the 768 declared Type. When you filter a set, howev r, you remove Units from796 declared Type. When you filter a set, however, you remove Units from 769 797 the whole which do not match the filter's Expression.</p> 770 798 … … 881 909 <tt>roster</tt>. A roster is like a three-way map between Unit classes, 882 910 their names, and their assigned StorageManagers. You shouldn't manipulate 883 this structure on your own; instead, use the <tt>register</tt> method to884 register each Unit class.</p>911 this structure on your own; instead, use the <tt>register</tt> or 912 <tt>register_all</tt> methods to register each Unit class.</p> 885 913 886 914 <p>The <tt>Arena</tt> object also manages the associations between Unit 887 915 classes in its <tt>associations</tt> attribute, which is a simple, 888 unweighted, undirected graph. In general, you should call 889 <tt>associate(cls, key, farClass, farKey)</tt> to add classes to this 890 graph. The only other common operation is to call 891 <tt>.associations.shortest_path(start, end)</tt>, to retrieve the 892 chain of associations between two Unit classes.</p> 916 unweighted, undirected graph. Whenever you register a class, the Arena 917 will add its associations to this graph. The only other common operation 918 is to call <tt>.associations.shortest_path(start, end)</tt>, to retrieve 919 the chain of associations between two Unit classes.</p> 893 920 894 921 <hr /> trunk/doc/storage.html
r37 r38 139 139 </ul> 140 140 141 <h5>Shelve</h5> 142 <p>Persists Units to shelve-type db files. Extremely simple implementation; 143 everything is pickled. Querying will be slow--every Unit is sucked in 144 one-by-one and tested in pure Python using <tt>Expression.evaluate()</tt>. 145 But for many applications, you don't need heavyweight query tools; 146 for example, an online forum may only need topic content looked up by ID. 147 Or small system tables that only get read at startup might benefit. 148 Configuration entries:</p> 149 <ul> 150 <li><b>Class:</b> <tt>dejavu.storage.storeshelve.StorageManagerShelve</tt></li> 151 <li><b>Path:</b> The file path (directory) in which to place db files. 152 Each Unit subclass will get its own file, of the same name as the 153 subclass.</li> 154 </ul> 155 141 156 142 157 <h4>Middleware</h4> … … 144 159 <p>Some Storage Managers act as "middleware", and can be chained together 145 160 to provide layered functionality. Consider, for example, the 146 <tt>CachingProxy</tt> class; it requires another Storage Manager161 <tt>CachingProxy</tt> class; it usually has another Storage Manager 147 162 "behind it", which it proxies. It can be used to cache objects between 148 163 client connections independently from the underlying, database-specific … … 153 168 154 169 <h5>Caching Proxy</h5> 155 <p>Use this class to persist Units between client connections. It needs156 another Storage Manager to proxy. Configuration entries:</p>170 <p>Use this class to persist Units in memory between client connections. 171 It usually proxies another Storage Manager. Configuration entries:</p> 157 172 <ul> 158 173 <li><b>Class:</b> <tt>dejavu.storage.CachingProxy</tt></li> 159 <li><b>Next Store:</b> Required. The name of the next Storage Manager 160 in the chain.</li> 174 <li><b>Next Store:</b> Optional. The name of the next Storage Manager 175 in the chain. If you do not specify a Next Store, Units will 176 only persist for the lifetime of the arena.</li> 161 177 <li><b>Lifetime:</b> Optional. The recurrence string which declares 162 178 how often to sweep Units out of the in-memory cache.</li> … … 164 180 165 181 <h5>Burned Proxy</h5> 166 <p>Use this class to persist Units between client connections. It needs167 another Storage Manager to proxy. Unlike the Caching Proxy above, this 168 Storage Manager recalls all Units at once upon the first request, and won't 169 recall them again from storage. They are "burned" into memory for the 170 lifetime of the application. Configuration entries:</p>182 <p>Use this class to persist Units in memory between client connections. 183 It needs another Storage Manager to proxy. Unlike the Caching Proxy above, 184 this Storage Manager recalls all Units at once upon the first request, 185 and won't recall them again from storage. They are "burned" into memory 186 for the lifetime of the application. Configuration entries:</p> 171 187 <ul> 172 188 <li><b>Class:</b> <tt>dejavu.storage.BurnedProxy</tt></li> trunk/engines.py
r37 r38 14 14 import pickle 15 15 import dejavu 16 from dejavu import logic 16 from dejavu import logic, associate 17 17 import xray 18 18 import sets … … 290 290 def rules(self): 291 291 """An ordered list of Rules for this Engine.""" 292 f = logic.filter(EngineID=self.ID) 293 allrules = [x for x in self.sandbox.recall(UnitEngineRule, f)] 292 allrules = [x for x in self.UnitEngineRule()] 294 293 allrules.sort(dejavu.sort(u'Sequence')) 295 294 return allrules … … 378 377 else: 379 378 return self.Owner in ('System', 'Public', user) 379 380 associate(UnitEngine, 'ID', UnitEngineRule, 'EngineID') 381 associate(UnitEngine, 'ID', UnitCollection, 'EngineID') 380 382 381 383 … … 597 599 B.release() 598 600 601 602 def register_classes(arena): 603 arena.register(UnitCollection) 604 arena.register(UnitEngine) 605 arena.register(UnitEngineRule) 606 trunk/storage/__init__.py
r37 r38 175 175 if self.nextstore: 176 176 self.nextstore.destroy(unit) 177 if unit.ID in cache:177 try: 178 178 del cache[unit.ID] 179 except KeyError: 180 pass 181 try: 179 182 del self._recallTimes[unit.ID] 183 except KeyError: 184 pass 180 185 finally: 181 186 lock.release() … … 189 194 if self.nextstore: 190 195 self.nextstore.reserve(unit) 196 else: 197 if unit.ID is None: 198 unit.ID = unit.sequencer.next(cache.keys()) 191 199 # Pickle the Unit to discard extraneous attributes, 192 200 # and avoid identity issues. trunk/storage/test_storeado.py
r37 r38 48 48 f = logic.Expression(lambda x: x.Legs == 4) 49 49 sql = testSM.multiselect(zoo.Animal, f, [(zoo.Zoo, None)])[0] 50 expected = ("SELECT [djvAnimal].[Options], [djvAnimal].[Legs], " 51 "[djvAnimal].[ID], [djvAnimal].[ZooID], " 52 "[djvAnimal].[Name], [djvZoo].[Founded], " 53 "[djvZoo].[LastEscape], [djvZoo].[ID], " 54 "[djvZoo].[Opens], [djvZoo].[Name] FROM [djvAnimal] " 55 "LEFT JOIN [djvZoo] ON [djvAnimal].[ZooID] = " 56 "[djvZoo].[ID] WHERE [djvAnimal].[Legs] = 4") 50 expected = ("SELECT [djvAnimal].[Legs], [djvAnimal].[Name], " 51 "[djvAnimal].[ZooID], [djvAnimal].[ID], " 52 "[djvAnimal].[LastEscape], [djvAnimal].[Options], " 53 "[djvZoo].[Founded], [djvZoo].[LastEscape], " 54 "[djvZoo].[ID], [djvZoo].[Opens], [djvZoo].[Name] " 55 "FROM [djvAnimal] LEFT JOIN [djvZoo] ON " 56 "[djvAnimal].[ZooID] = [djvZoo].[ID] WHERE " 57 "[djvAnimal].[Legs] = 4") 57 58 self.assertEqual(sql, expected) 58 59 trunk/test_dejavu.py
r37 r38 1 1 import unittest 2 2 import datetime 3 3 4 import dejavu 5 from dejavu import zoo, storage 6 zoo.arena.add_store("default", storage.CachingProxy("default", zoo.arena)) 4 7 5 8 … … 7 10 8 11 def test_Properties(self): 9 a = dejavu.Unit() 12 # Instance creation and population 13 f = datetime.date(1916, 10, 2) 14 z = zoo.Zoo(Name='San Diego Zoo', Founded=f) 15 self.assertEqual(z.dirty(), True) 16 self.assertEqual(zoo.Zoo.ID.type, int) 17 self.assertEqual(z.ID, None) 18 self.assertEqual(z.Name, 'San Diego Zoo') 19 self.assertEqual(type(z.Name), unicode) 20 self.assertEqual(z.Founded, f) 21 self.assertEqual(z.__class__.ID.index, True) 10 22 23 a = zoo.Animal(Name='Giraffe', Legs=4) 24 self.assertEqual(a.dirty(), True) 11 25 self.assertEqual(a.ID, None) 12 self.assertEqual(a.__class__.ID.type, int) 13 a.ID = 321 14 self.assertEqual(a.ID, 321) 15 self.assertEqual(a.dirty(), True) 16 a.ID = '444' 17 self.assertEqual(a.ID, 444) 18 # Should remapping the property attempt to coerce all instances? 19 a.set_property('ID', unicode) 20 a.ID = 'my thing' 21 self.assertEqual(a.ID, 'my thing') 26 self.assertEqual(a.Name, 'Giraffe') 27 self.assertEqual(a.Legs, 4) 28 self.assertEqual(a.__class__.ZooID.index, True) 22 29 23 class Triggered(dejavu.UnitProperty): 24 def pre(self, unit, value): 25 unit.preval = value 26 27 def post(self, unit, value): 28 unit.postval = value 30 # Sandboxing 31 s = zoo.arena.new_sandbox() 32 s.memorize(z) 33 self.assertEqual(z.ID, 1) 34 s.memorize(a) 35 self.assertEqual(a.ID, 1) 36 z.add(a) 37 self.assertEqual(a.ZooID, 1) 29 38 30 class Thing(dejavu.Unit): 31 Integer = dejavu.UnitProperty(int, index=True) 32 String = dejavu.UnitProperty(str, hints={'Size': 0}) 33 Unicode = dejavu.UnitProperty(unicode) 34 Datetime = Triggered(datetime.datetime) 35 Dict = dejavu.UnitProperty(dict) 36 37 # Instance creation and population 38 t = Thing(Integer=3, String='abc', Unicode=u'foo') 39 self.assertEqual(t.dirty(), False) 40 self.assertEqual(t.Integer, 3) 41 self.assertEqual(t.String, 'abc') 42 self.assertEqual(type(t.String), str) 43 self.assertEqual(t.Unicode, u'foo') 44 self.assertEqual(type(t.Unicode), unicode) 45 self.assertEqual(t.Datetime, None) 46 self.assertEqual(t.Dict, None) 47 48 # Index attribute 49 self.assertEqual(t.__class__.Integer.index, True) 50 51 # Triggers. Unit needs a sandbox or pre/post won't be called. 52 t.sandbox = True 53 self.assertEqual(hasattr(t, 'preval'), False) 54 t.Datetime = aDate = datetime.datetime(2004, 10, 20) 55 self.assertEqual(t.preval, aDate) 56 self.assertEqual(t.postval, aDate) 57 self.assertEqual(t.dirty(), True) 58 59 t.cleanse() 60 t.Dict = aDict = {'k': 'blah'} 61 self.assertEqual(t.dirty(), True) 62 self.assertEqual(t.Dict, aDict) 39 # Triggers 40 z.cleanse() 41 self.assertEqual(z.dirty(), False) 42 a.LastEscape = d = datetime.datetime(2004, 10, 20) 43 self.assertEqual(a.LastEscape, d) 44 self.assertEqual(z.LastEscape, d) 45 self.assertEqual(z.dirty(), True) 46 63 47 64 48 if __name__ == "__main__": trunk/zoo.py
r37 r38 1 1 import datetime 2 import dejavu 3 from dejavu import Unit, UnitProperty, associate 2 4 3 import dejavu4 from dejavu import Unit, UnitProperty5 6 arena = dejavu.Arena()7 5 8 6 class Zoo(Unit): … … 13 11 14 12 13 class EscapeProperty(UnitProperty): 14 def post(self, unit, value): 15 z = unit.first(Zoo) 16 if z: 17 z.LastEscape = unit.LastEscape 18 15 19 class Animal(Unit): 16 20 Name = UnitProperty() 17 ZooID = UnitProperty(int )21 ZooID = UnitProperty(int, index=True) 18 22 Legs = UnitProperty(int) 19 23 Options = UnitProperty(dict) 24 LastEscape = EscapeProperty(datetime.datetime) 20 25 21 arena.associate(Zoo, 'ID', Animal, 'ZooID') 26 associate(Zoo, 'ID', Animal, 'ZooID') 27 22 28 23 29 class Exhibit(Unit): … … 25 31 Animals = UnitProperty(list) 26 32 27 arena.associate(Zoo, 'ID', Exhibit, 'ZooID') 33 associate(Zoo, 'ID', Exhibit, 'ZooID') 34 35 arena = dejavu.Arena() 36 arena.register_all(globals())
