Changeset 543
- Timestamp:
- 10/06/07 22:38:27
- Files:
-
- trunk/dejavu/__init__.py (modified) (3 diffs)
- trunk/dejavu/doc/index.html (modified) (6 diffs)
- trunk/dejavu/doc/managing.html (modified) (24 diffs)
- trunk/dejavu/doc/modeling.html (modified) (15 diffs)
- trunk/dejavu/doc/storage.html (modified) (39 diffs)
- trunk/dejavu/engines.py (modified) (1 diff)
- trunk/dejavu/sandboxes.py (modified) (12 diffs)
- trunk/dejavu/storage/__init__.py (modified) (5 diffs)
- trunk/dejavu/storage/caching.py (modified) (1 diff)
- trunk/dejavu/storage/storefs.py (modified) (2 diffs)
- trunk/dejavu/storage/storejson.py (modified) (2 diffs)
- trunk/dejavu/storage/storememcached.py (modified) (2 diffs)
- trunk/dejavu/storage/storeram.py (modified) (3 diffs)
- trunk/dejavu/storage/storeshelve.py (modified) (3 diffs)
- trunk/dejavu/test/test_dejavu.py (modified) (3 diffs)
- trunk/dejavu/test/zoo_fixture.py (modified) (7 diffs)
- trunk/dejavu/units.py (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/dejavu/__init__.py
r542 r543 38 38 import datetime as _datetime 39 39 40 from geniusql import logic 41 from geniusql import logicfuncs 40 from geniusql import logic, logicfuncs, _AttributeDocstrings 42 41 43 42 from dejavu import logflags … … 51 50 52 51 class Query(object): 53 """A query with relation, attributes, and restriction expressions. 52 """A query with relation, attributes, and restriction expressions.""" 54 53 55 relation: Either a single Unit class, or a UnitJoin (of Unit classes). 56 attributes: If None (the default), all attributes are returned. 57 Otherwise, if the relation is a single Unit class, this value 58 must be a sequence of attribute names for that class. If the 59 relation is a UnitJoin, this must be a sequence of sequences 60 of attribute names. That is: 61 [('ID', 'Size', ...), 62 ('ID', 'Value', ...), 63 ...] 64 The order of sequences must match the order of classes given in the 65 relation (and therefore the restriction args, if applicable). 66 A final option is to pass a lambda (or Expression) which returns 67 the attributes as a tuple or list; e.g.: 68 lambda x, y: (x.a, x.b - now(), x.c + y.a) 69 This allows access to binary operations and builtin functions. 70 restriction: an Expression (or lambda) to restrict the rows returned; 71 for SQL backends, this will be used to construct a WHERE clause. 72 The args must be in the same order as the classes in the relation. 73 **kwargs: additional expr filters in name=value format. 74 """ 54 __metaclass__ = _AttributeDocstrings 75 55 76 def __init__(self, relation, attributes=None, restriction=None, **kwargs): 56 relation = None 57 relation__doc = """A single Unit class or a UnitJoin (of Unit classes).""" 58 59 attributes = None 60 attributes__doc = """ 61 If None (the default), all attributes are returned. 62 Otherwise, if the relation is a single Unit class, this value 63 must be a sequence of attribute names for that class. If the 64 relation is a UnitJoin, this must be a sequence of sequences 65 of attribute names. That is: 66 [('ID', 'Size', ...), 67 ('ID', 'Value', ...), 68 ...] 69 The order of sequences must match the order of classes given in the 70 relation (and therefore the restriction args, if applicable). 71 A final option is to pass a lambda (or Expression) which returns 72 the attributes as a tuple or list; e.g.: 73 lambda x, y: (x.a, x.b - now(), x.c + y.a) 74 This allows access to binary operations and builtin functions.""" 75 76 restriction = None 77 restriction__doc = """ 78 An Expression (or lambda, or dict) to restrict the rows returned. 79 80 For SQL backends, this will be used to construct a WHERE clause. 81 The args must be in the same order as the classes in the relation.""" 82 83 def __init__(self, relation, attributes=None, restriction=None): 77 84 self.relation = relation 78 85 … … 84 91 if restriction is None: 85 92 restriction = logic.Expression(lambda *args: True) 93 elif isinstance(restriction, dict): 94 restriction = logic.filter(**restriction) 86 95 elif not isinstance(restriction, logic.Expression): 87 96 restriction = logic.Expression(restriction) 88 restriction = logic.combine(restriction, kwargs)89 97 90 98 self.restriction = restriction trunk/dejavu/doc/index.html
r542 r543 32 32 <li>Unit Properties are First-Class Objects</li> 33 33 <li>Triggers</li> 34 <li>Registration of Unit Classes</li> 35 <li>Synchronizing the Model</li> 36 <li>Subclasses and Inheritance</li> 34 <li><a href='modeling.html#registering'>Registration of Unit Classes</a></li> 35 <li><a href='modeling.html#synchronizing'>Synchronizing the Model</a></li> 37 36 </ul> 38 37 </li> … … 44 43 </ul> 45 44 </li> 46 <li><a href='modeling.html#stores'>Storage Managers</a>47 <ul>48 <li><a href='modeling.html#registering'>Registering Unit Classes</a></li>49 </ul>50 </li>51 45 <li><a href='modeling.html#schemas'>Managing Schemas</a> 52 46 <ul> 47 <li>Conflicts</li> 53 48 <li>Installation</li> 54 49 <li>Modifying Storage Structures</li> … … 102 97 <li><a href='storage.html'>Deployers: Configuring Storage</a> 103 98 <ul> 104 <li><a href='storage.html#configuration'>Common Configuration Entries</a></li>105 99 <li><a href='storage.html#databases'>Database Storage Managers</a> 106 100 <ul> … … 116 110 <ul> 117 111 <li>RAM</li> 112 <li>Memcached</li> 113 <li>JSON</li> 118 114 <li>Shelve</li> 119 115 <li>Folders</li> … … 122 118 <li><a href='storage.html#middleware'>Middleware</a> 123 119 <ul> 124 <li>Caching Proxy</li> 125 <li>Burned Proxy</li> 120 <li>Object Cache</li> 121 <li>Aged Cache</li> 122 <li>Burned Cache</li> 123 </ul> 124 </li> 125 <li><a href='storage.html#partitioning'>Partitioning</a> 126 <ul> 127 <li>Vertical Partitioner</li> 126 128 </ul> 127 129 </li> … … 132 134 <ul> 133 135 <li>Subclassing Sandbox</li> 134 <li>Stor Hacking</li>136 <li>Store Hacking</li> 135 137 <li>Passing through SQL</li> 136 138 <li>Custom Storage Managers trunk/dejavu/doc/managing.html
r542 r543 20 20 Storage Manager.</p> 21 21 22 <p>You can create Sandbox objects directly. They take a single argument, the23 top-level <tt>Arena</tt> object. Arenas also provide a convenience function, 24 <tt class='def'>new_sandbox</tt>, which does this for you. The following 25 lines are equivalent:26 <pre>box = dejavu.Sandbox( myArena)27 28 box = myArena.new_sandbox()</pre>29 You might often choose the latter when you have a reference to the Arena30 object,and would rather avoid importing dejavu yet again just to obtain22 <p>You can create Sandbox objects directly. They take a single argument, a 23 <tt>StorageManager</tt> object. All Storage Managers also provide a 24 convenience function, <tt class='def'>new_sandbox</tt>, which does this 25 for you. The following lines are equivalent: 26 <pre>box = dejavu.Sandbox(store) 27 28 box = store.new_sandbox()</pre> 29 You might often choose the latter when you have a reference to the store, 30 and would rather avoid importing dejavu yet again just to obtain 31 31 the Sandbox class.</p> 32 32 … … 44 44 45 45 <p>Memorization does several things. First, it places your new Unit into 46 your Arena. That Unit instance will now be persisted by the appropriate46 your store. That Unit instance will now be persisted by the appropriate 47 47 Storage Manager. It can be recalled from storage when needed, using the 48 48 built-in Expression syntax. It may have been given an ID (see … … 63 63 you like. As long as you provide your own identifier values for Units, 64 64 nothing will break--you can memorize and recall Units without problems. 65 However, if you memorize a Unit with an ID of <tt>None</tt>, the Sandbox65 However, if you memorize a Unit with an ID of <tt>None</tt>, the store 66 66 may attempt to provide an ID for it.</p> 67 67 … … 86 86 accomplish this.</p> 87 87 88 < h5>recall()</h5>88 <a name='recall'><h5>recall()</h5></a> 89 89 <p>First, the appropriately named 90 <tt class='def'>recall(cls, expr=None, inherit=False, **kwargs)</tt> function.91 This is the full-blown query method. As a first argument, you pass it the 92 class (<b>not</b> the name of the class, but the actual class) of which you 93 expect to retrieve instances. The second argument should be a lambda, or an instance94 of <tt>dejavu.logic.Expression</tt>, an object which encapsulates your 95 specific query (see <a href='#Querying'>Querying</a>).96 Alternately, you may supply keyword arguments, which will then be97 combined into an Expression for you.90 <tt class='def'>recall(cls, expr=None, order=None, limit=None, offset=None)</tt> 91 function. This is the full-blown query method. As a first argument, you 92 pass it the class (<b>not</b> the name of the class, but the actual class) 93 of which you expect to retrieve instances. The second argument should be 94 a lambda or an instance of <tt>dejavu.logic.Expression</tt> (an object which 95 encapsulates your specific query, see <a href='#Querying'>Querying</a>). 96 Alternately, you may supply a dict, which will then be combined into an 97 Expression for you (using <a href='#filter'><tt>logic.filter</tt></a>). 98 98 The following three examples are equivalent and all result in the same output: 99 <pre>>>> units = box.recall(Book, Year=1928)99 <pre>>>> units = box.recall(Book, dict(Year=1928)) 100 100 >>> units = box.recall(Book, lambda x: x.Year == 1928) 101 101 >>> units = box.recall(Book, logic.Expression(lambda x: x.Year == 1928)) … … 104 104 u'Tarzan, The Lord of the Jungle'] 105 105 </pre> 106 If you do not supply an Expression or any keyword args, all Units of the106 If you do not supply an expression, all Units of the 107 107 given Unit class will be retrieved in a list.</p> 108 108 … … 110 110 called when each Unit has been loaded from storage (at the end of the 111 111 recall process). Once the unit is loaded into a Sandbox, however, 112 <tt>on_recall</tt> will not be called ; it's only called at the Sandbox/SM113 boundary. If <tt>on_recall</tt> raises <tt>UnrecallableError</tt>, the 114 unit will not be yielded back to the caller, nor placed in the Sandbox112 <tt>on_recall</tt> will not be called again; it's only called at the 113 Sandbox/SM boundary. If <tt>on_recall</tt> raises <tt>UnrecallableError</tt>, 114 the unit will not be yielded back to the caller, nor placed in the Sandbox 115 115 cache.</p> 116 116 117 < h5>Recalling multiple classes at once (JOINs)</h5>117 <a name='joins'><h5>Recalling multiple classes at once (JOINs)</h5></a> 118 118 119 119 <p>In addition to providing a single class to <tt>recall</tt>, you have … … 122 122 instances.</p> 123 123 124 125 124 <p>The "leftbiased" argument specifies how the results will be joined:</p> 126 125 127 126 <table> 128 <tr><th>leftbiased</th><th>Join Type</th><th>Description</th><th>Operator</th></tr> 127 <tr> 128 <th>leftbiased</th> 129 <th>Join Type</th> 130 <th>Description</th> 131 <th>Operator</th> 132 </tr> 129 133 <tr> 130 134 <td>None</td> … … 165 169 <p>When you provide multiple classes, the <tt>recall</tt> method returns 166 170 a list of rows. Each row will be a list of units, one per class in the 167 <tt>classes</tt> arg. The <tt>expr</tt> arg should be a 168 <tt>logic.Expression</tt> which can evaluate all of the 169 units in any given row at once.171 <tt>classes</tt> arg. The <tt>expr</tt> arg should be a lambda or 172 <tt>logic.Expression</tt> which can evaluate all of the units in any 173 given row at once (you cannot use a dict expr with multiple classes). 170 174 171 175 <pre>for pub, book in box.recall(Publisher & Book, lambda p, b: p.ID == 4)</pre> … … 184 188 185 189 <p>The relationships (joins) between each class are specified by 186 <a href='modeling.html#associations'>Unit Associations</a> ).</p>190 <a href='modeling.html#associations'>Unit Associations</a>.</p> 187 191 188 192 <h5>xrecall()</h5> … … 193 197 <p>The <tt>recall</tt> method can be verbose. When you want a one-liner 194 198 and only expect a single Unit, use the 195 <tt class='def'>unit(cls, expr=None, inherit=False,**kw)</tt> method199 <tt class='def'>unit(cls, **kw)</tt> method 196 200 of Sandboxes. Again, you pass the class of Units you wish to retrieve 197 as the first argument. Then, supply a logic.Expression, or keyword arguments 198 of the form 201 as the first argument. Then, supply keyword arguments of the form 199 202 "property_name=value". The method will form an equivalent Expression 200 203 for you from the keyword args. For example: … … 205 208 If no Unit can be found that matches the criteria, None is returned. 206 209 If multiple Units match the criteria, only the first one is returned 207 (although the rest are probably loaded into memory).</p> 210 (although the rest may be loaded into memory).</p> 211 212 <p>The <tt>unit</tt> method is heavily optimized (in both the sandbox and 213 all stores) for retrieving a single Unit by its identifiers. When using 214 key-value stores like <a href='http://www.danga.com/memcached/'>memcached</a> 215 in your storage manager network, calling <tt>unit</tt> may be much faster 216 than <tt>recall</tt>, even for multiple units.</p> 208 217 209 218 <h5>"Magic recaller" methods</h5> 210 <p>For each class you have registered with your Arena, the Sandbox will219 <p>For each class you have registered with your store, the Sandbox will 211 220 have a "magic recaller" method of the same name, to make single-unit 212 221 lookups easier. Instead of the above example for <tt>box.unit()</tt>, … … 263 272 units in the sandbox. For example, if you do this:</p> 264 273 265 <pre>box = arena.new_sandbox()274 <pre>box = store.new_sandbox() 266 275 thing = box.unit(Thing) 267 276 box.flush_all() … … 279 288 <h4>Views</h4> 280 289 <p>Sandboxes provide a 281 <tt class='def'>view( cls, attrs, expr=None, **kwargs)</tt> function.290 <tt class='def'>view(query, distinct=False)</tt> function. 282 291 This works like <tt>recall</tt>, but returns values, rather than Units. 292 The 'query' argument should be an instance of <tt>dejavu.Query</tt>, 293 or more commonly, a 3-tuple of (cls, attrs, expr). 283 294 Put simply, it yields all values 284 295 for the given attribute(s) of the Unit class provided; each unit will 285 296 yield a tuple of its values in the same order as the <tt>attrs</tt> 286 sequence you provide. Providing an expr argument (either a lambda or an 287 <tt>Expression</tt> 288 object, see below), or keyword arguments, will filter the set of Units 289 before obtaining the value tuples.</p> 290 291 <pre>>>> v = sandbox.view(zoo.Animal, ['Name', 'Lifespan']) 297 sequence you provide. Providing an expr argument (a lambda, an 298 <tt>Expression</tt> object, or a dict, see below), will filter the 299 set of Units before obtaining the value tuples.</p> 300 301 <pre>>>> v = sandbox.view((zoo.Animal, ['Name', 'Lifespan'])) 292 302 >>> [row for row in v] # or list(v), or iterate over v... 293 303 [('Leopard', 73.5), … … 308 318 be an iterable.</p> 309 319 310 <p>Sandboxes also provide a 311 <tt class='def'>distinct(cls, attrs, expr=None, **kwargs)</tt> 312 function. This works just like <tt>view</tt>, but returns distinct 320 <p>If the 'distinct' argument is True, <tt>view</tt> returns distinct 313 321 tuples rather than all tuples.</p> 314 322 315 <p>The <tt>distinct</tt> function can also be used as a <tt>count</tt> 316 function by passing <tt>attrs = cls.identifiers</tt>. 317 Sandboxes provide a <tt class='def'>count(cls, expr=None, **kwargs)</tt> 323 <p>The <tt>view</tt> function can also be used as a <tt>count</tt> 324 function by passing <tt>attrs = cls.identifiers</tt> and setting 325 'distinct' to True. 326 Sandboxes provide a <tt class='def'>count(cls, expr=None)</tt> 318 327 method which does just this.</p> 319 328 320 <p> Dejavu 1.5 adds two new sandbox methods: range and sum. The321 <tt class='def'>range(cls, attr, expr, **kw)</tt> method takes a single329 <p>There are two additional sandbox methods for aggregates: range and sum. 330 The <tt class='def'>range(cls, attr, expr=None)</tt> method takes a single 322 331 attribute and returns the closed interval [min(attr), ..., max(attr)]. The 323 <tt class='def'>sum(cls, attr, expr , **kw)</tt> method also takes a single332 <tt class='def'>sum(cls, attr, expr=None)</tt> method also takes a single 324 333 attribute and returns the sum of all non-None values for the given 325 334 cls.attr.</p> 326 335 336 <h5>xview()</h5> 337 <p>Just like view, but returns an iterator instead of a list. Use xview 338 to load Unit values in a more lazy fashion.</p> 339 340 327 341 328 342 <h4>Transactions</h4> 329 343 <p>Dejavu supports distributed transactions at all levels (however, it 330 does not yet use distributed two-phase commit! That's planned for 1.6).344 does not yet use distributed two-phase commit! That's planned for later). 331 345 Most often, your code will call transaction methods on the current 332 346 sandbox object. … … 488 502 489 503 <p>Second, any globals or cell references must <b>also</b> be present in 490 the <tt>logic</tt> module 's globalswhen the Expression is unpickled.504 the <tt>logic</tt> module when the Expression is unpickled. 491 505 Pickling occurs when Expressions are sent over sockets, and also if 492 506 Expressions are themselves persisted to storage (for example, see 493 <u>Unit Engines</u>, below). This means your application should inject494 globals into the <tt>logic</tt> module. Note that the <tt>logic</tt> module 495 already tries to import <tt>datetime</tt>, <tt>fixedpoint</tt> and 496 <tt> decimal</tt>.</p>507 <u>Unit Engines</u>, below). This means your application must register 508 such global references in <tt>logic.builtins</tt> (a dict). Note that 509 the <tt>logic</tt> module already tries to import <tt>datetime</tt>, 510 <tt>fixedpoint</tt> and <tt>decimal</tt>.</p> 497 511 498 512 <h4>External functions within Expressions</h4> 499 513 <p>Dejavu provides additional functions which can be used in Expressions. 500 514 For example, you can construct an Expression like: 501 <pre>logic.Expression(lambda x: x.Size < 3 and x.Date > dejavu.today())</pre>515 <pre>logic.Expression(lambda x: x.Size < 3 and x.Date > today())</pre> 502 516 In this example, the <tt>today()</tt> function breaks convention and is 503 517 actually <b>bound late</b>. That is, if you construct this Expression now … … 505 519 Storage Managers "know about" these dejavu functions, and can use them 506 520 to build more appropriate queries. Here are the functions supplied by 507 the <tt>dejavu</tt> module:</p>521 Dejavu:</p> 508 522 509 523 <table> … … 553 567 <td>Y</td> 554 568 <td>datetime.datetime.now()</td> 569 </tr> 570 <tr> 571 <td><tt>utcnow()</tt></td> 572 <td>Y</td> 573 <td>datetime.datetime.utcnow()</td> 555 574 </tr> 556 575 <tr> … … 581 600 say, SQL), and thereby achieve end-to-end functionality.</p> 582 601 583 < h4>Using <tt>filter</tt> to form Expressions</h4>602 <a name='filter'><h4>Using <tt>filter</tt> to form Expressions</h4></a> 584 603 <p>The <tt>logic</tt> module also provides convenient methods to 585 604 create common types of Expression objects via the <tt>filter</tt> and … … 622 641 Although the comparison function only allows a single comparison at a time, 623 642 the resulting Expressions can be combined with the <tt>&</tt> and <tt>|</tt> 624 operators ( described earlier) to produce more complex Expressions.</p>643 operators (see next) to produce more complex Expressions.</p> 625 644 626 645 <h4>Combining Expressions</h4> … … 671 690 <p>In particular, <tt>logic.Expression</tt> objects can operate on <i>any</i> 672 691 Python objects, not just dejavu <tt>Unit</tt> instances. If you wish to 673 provide additional logic functions (as dejavu does), simply injectthem674 into <tt>logic</tt>'s globals.</p>692 provide additional logic functions (as dejavu does), simply add them 693 to <tt>logic.builtins</tt>.</p> 675 694 676 695 <p>You may also find the underlying <tt>codewalk</tt> module useful for … … 785 804 <tr> 786 805 <td>FUNCTION</td> 787 <td>The name of a function in the <tt> Arena.engine_functions</tt>806 <td>The name of a function in the <tt>store.engine_functions</tt> 788 807 dict</td> 789 808 <td>Calls the function, passing the current Set. The function … … 795 814 <td>Transform the current Set into a Set of associated Units 796 815 (of another Type). The association must be present in the 797 <tt> Arena.associations</tt> graph.</td>816 <tt>store.associations</tt> graph.</td> 798 817 </tr> 799 818 <tr> … … 864 883 <h4>Engine Functions</h4> 865 884 <p>The FUNCTION rule deserves special mention. The Operand of a FUNCTION 866 rule is a string, a key in the <tt> Arena.engine_functions</tt> dictionary.885 rule is a string, a key in the <tt>store.engine_functions</tt> dictionary. 867 886 When the rule is executed, that key is used to look up the function, which 868 887 is then called, passing <tt>(sandbox, set)</tt>. The function should … … 877 896 878 897 <h4>Sorting Units</h4> 879 <p>When you recall Units, you receive a list. However, the <tt>recall</tt>880 method doesn't do any sorting; you must sort your list in your Python code. 881 Dejavu provides a <tt class='def'>sort(attrs, descending=False)</tt>882 function to assist 883 you in sorting Units. It returns a function, which you can then use in 884 Python's sort function (which operates in place).Continuing our example:885 <pre>people.sort(dejavu.sort( 'Size', 'Name'))</pre>898 <p>When you recall Units, you receive a list. If you didn't ask the 899 <tt>recall</tt> method to order the results, you must sort your list 900 in your Python code. Dejavu provides a <tt class='def'>sort(attrs)</tt> 901 function to assist you in sorting Units. It returns a function, which you 902 can then use in Python's sort function (which operates in place). 903 Continuing our example: 904 <pre>people.sort(dejavu.sort(['Size DESC', 'Name']))</pre> 886 905 The most important issue (and the reason we don't just use 2.4's attrgetter), 887 906 is that any Unit property must allow values of None, which tends to raise trunk/dejavu/doc/modeling.html
r542 r543 86 86 </pre> 87 87 88 <p>Every Unit mustpossess at least one identifier. This ensures that88 <p>Every Unit should possess at least one identifier. This ensures that 89 89 each Unit within the system is unique. You should consider any 90 90 UnitProperty which is one of the identifiers to be read-only 91 after a Unit has been memorized.</p> 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> 92 95 93 96 <h4>Creating and Populating Properties</h4> … … 245 248 already been set on the unit.</p> 246 249 247 < h4>Registration of Unit Classes</h4>250 <a name='registering'><h4>Registration of Unit Classes</h4></a> 248 251 <p>In addition to defining your Unit class, you must also register that 249 252 class with your application's <tt>StorageManager</tt>. Each class which … … 259 262 you have defined between Units.</p> 260 263 261 262 <h4>Synchronizing the Model</h4> 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> 263 280 264 281 <p>Any database code in a general-purpose programming language will … … 272 289 to know the database types in effect in order to translate data safely.</p> 273 290 274 <p>By default, Dejavu automatically looks up that type information 275 for each Unit class the first time you try to use it. This is fine for 276 development purposes, but when you go into production, it may result in 277 slow access times for the first user of any given Unit class. If you 278 would rather have Dejavu pre-load all of this information, you can call 279 <tt class='def'>store.map_all()</tt> <i>after</i> you have registered 280 all of your Unit classes (but before you attempt to execute 281 commands on them).</p> 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> 282 295 283 296 <p>If your application has created all of its own tables using Dejavu, … … 292 305 293 306 294 <h4>Subclasses and Inheritance</h4>295 <p>Starting in 1.4, you can use superclasses to recall all subclasses.296 For example:</p>297 298 <p style='margin-left: 40px;'><code>299 class Payment(Unit): pass300 <br />class Cash(Payment): pass301 <br />class Credit(Payment): pass302 </code></p>303 304 <p>...time passes and instances of each class are memorized...</p>305 306 <p style='margin-left: 40px;'><code>307 sandbox.recall(Payment, <b>inherit=True</b>)308 <br />[<bill.Payment object at 0x01158E10>,309 <br /> <bill.Cash object at 0x0118B350>,310 <br /> <bill.Credit object at 0x0118B170>]311 </code></p>312 313 <p>This also works for the <tt>xrecall</tt> and <tt>unit</tt> methods, but314 only when providing a single class (none of them retrieve subclasses when315 recalling joined classes yet). Currently, you cannot reference a property316 (in an Expression) which is not present in both the superclass and all of317 the subclasses.</p>318 319 <p><b>The 'inherit' argument is new in 1.5, and defaults to False.</b></p>320 321 307 <a name='associations'><h3>Associations between Unit Classes</h3></a> 322 308 <p>Once you've put together some Unit classes, chances are you're going to … … 346 332 relationship objects.</p> 347 333 348 <p>What does an explicit association buy for you? First, you can associate349 Units without having to remember which keys are related. Second, 350 StorageManagers334 <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 351 337 discover associations and fill the <tt>store.associations</tt> registry, so 352 338 that smart consumer code (like <a href='managing.html#unitenginerules'>Unit … … 397 383 <pre>>>> Archaeologist.Biography 398 384 <unbound method Archaeologist.related_units> 385 399 386 >>> Eversley = Archaeologist(Height=6.417) 400 387 >>> Eversley.Biography 401 388 <bound method Archaeologist.related_units of <__main__.Archaeologist 402 389 object at 0x011A1930>> 390 403 391 >>> bios = Eversley.Biography() 404 392 >>> bios … … 412 400 easily. At the other extreme (when you have hundreds of Biographies to filter), 413 401 you can pass an optional <tt>Expression</tt> object or keyword arguments 414 to the "related units" method, just like you can with <tt>recall</tt>. 402 to the "related units" method, just like you can with 403 <a href='managing.html#recall'><tt>recall</tt></a>. 415 404 When you do, the list of associated Units will be filtered accordingly.</p> 416 405 … … 422 411 (or None if there is no matching Unit). When retrieving "to-many", 423 412 the result will always be a list, (it will be empty if there are 424 no matches). This is new for Dejavu 1.4 (previously, they both 425 would have returned lists).</p> 413 no matches).</p> 426 414 427 415 <p>Because the "related units" method names are formed automatically, you need … … 457 445 458 446 def related(self, unit, expr=None, **kwargs): 459 bios = unit.Biography(expr, **kwargs)460 if bios:461 bios.sort(dejavu.sort("PubDate"))462 return bios[-1]463 return None447 bios = unit.Biography(expr, order=["PubDate DESC"], **kwargs) 448 try: 449 return bios.next() 450 except StopIteration: 451 return None 464 452 465 453 descriptor = LastBiographyAssociation(u'ID', Biography, u'ID') … … 475 463 Unit, or None. Finally, the <tt>register</tt> attribute, when False, 476 464 keeps the store from registering this association in its graph 477 (see <a href='#registering'>Registering</a>, below).</p> 478 479 <a name='stores'><h3>Storage Managers</h3></a> 480 <p>The topmost object in Dejavu is a <tt>StorageManager</tt> object. 481 When building a Dejavu application, you must first create a 482 StorageManager instance, and must find a way to persist this object 483 across client connections. This can be achieved in multiple ways; 484 web applications, for example, will typically create a single process 485 to serve all requests. Desktop applications will probably create a 486 single StorageManager object for each running instance of the program.</p> 487 488 <a name='registering'><h4>Registering Unit Classes</h4></a> 489 <p>The <tt>StorageManager</tt> object maintains a registry of Unit 490 classes (<tt class='def'>.classes</tt>). You shouldn't manipulate this 491 structure on your own; instead, use the <tt class='def'>register(cls)</tt> 492 or <tt class='def'>register_all(globals())</tt> methods to register each 493 Unit class.</p> 494 495 <p>Each <tt>StorageManager</tt> object also manages the associations 496 between Unit classes in its <tt class='def'>associations</tt> attribute, 497 which is a simple, unweighted, undirected graph. Whenever you register 498 a Unit class, the SM will add its associations to this graph. The only 499 other common operation is to call 500 <tt class='def'>associations.shortest_path(start, end)</tt>, 501 to retrieve the chain of associations between two Unit classes.</p> 465 (see <a href='#registering'>Registration</a>, above).</p> 502 466 503 467 504 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> 505 497 506 498 <h4>Installation</h4> … … 509 501 Dejavu doesn't try to over-engineer it. But the deployer will still have 510 502 to go through an installation step at some point. Dejavu offers minimal 511 library calls which you can then build installation (and upgrade, and512 uninstall) tools on top of.</p>503 library calls on top of which you can then build installation tools 504 (and upgrade, and uninstall tools).</p> 513 505 514 506 <p>For example, a simple install process could look like this:</p> … … 519 511 store.logflags = logflags.ERROR + logflags.SQL + logflags.SANDBOX 520 512 521 print "Creating databases..." 522 for store in store.stores.itervalues(): 523 # Do not auto-discover db schema 524 store.auto_discover = False 525 store.create_database() 526 527 print "Creating tables..." 528 for cls in store._registered_classes: 529 store.create_storage(cls) 530 531 print "done" 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 532 521 sys.exit(0) 533 522 </pre> 534 523 535 <p>In addition to <tt class='def'>create_database()</tt>, all Storage 536 Managers also have a <tt class='def'>drop_database()</tt> method.</p> 537 538 <p><b>IMPORTANT:</b> When doing an initial install, you must turn off 539 the automatic mapping of model classes to database schema objects 540 (since the database objects don't exist yet!). Do this by setting 541 store.<tt class='def'>auto_discover</tt> to <tt>False</tt>, 542 as shown above.</p> 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> 543 527 544 528 <h4>Modifying Storage Structures</h4> … … 560 544 <p>Assuming we've already made the change to our model, the above example 561 545 renames the property in the persistence layer (the database) using the 562 <tt class='def'>rename_property(cls, oldname, newname )</tt> method.563 Additional <tt>StorageManager</tt> methods:</p>546 <tt class='def'>rename_property(cls, oldname, newname, conflicts='error')</tt> 547 method. Additional <tt>StorageManager</tt> methods:</p> 564 548 565 549 <p>Unit classes (tables):</p> 566 550 <ul> 567 <li><tt class='def'>create_storage(cls )</tt></li>551 <li><tt class='def'>create_storage(cls, conflicts='error')</tt></li> 568 552 <li><tt class='def'>has_storage(cls)</tt></li> 569 <li><tt class='def'>drop_storage(cls )</tt></li>553 <li><tt class='def'>drop_storage(cls, conflicts='error')</tt></li> 570 554 </ul> 571 555 572 556 <p>Unit properties (columns):</p> 573 557 <ul> 574 <li><tt class='def'>add_property(cls, name )</tt></li>558 <li><tt class='def'>add_property(cls, name, conflicts='error')</tt></li> 575 559 <li><tt class='def'>has_property(cls, name)</tt></li> 576 <li><tt class='def'>drop_property(cls, name )</tt></li>560 <li><tt class='def'>drop_property(cls, name, conflicts='error')</tt></li> 577 561 </ul> 578 562 579 563 <p>Unit property (column) indices:</p> 580 564 <ul> 581 <li><tt class='def'>add_index(cls, name )</tt></li>565 <li><tt class='def'>add_index(cls, name, conflicts='error')</tt></li> 582 566 <li><tt class='def'>has_index(cls, name)</tt></li> 583 <li><tt class='def'>drop_index(cls, name )</tt></li>567 <li><tt class='def'>drop_index(cls, name, conflicts='error')</tt></li> 584 568 </ul> 585 569 … … 654 638 655 639 <p>When you create your first Dejavu model, you might be forming it 656 to match some existing database schema. If so, Dejavu 1.5has a657 new<tt>Modeler</tt> tool to help you inside <tt>dejavu.storage.db</tt>.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>. 658 642 </p> 659 643 trunk/dejavu/doc/storage.html
r542 r543 24 24 25 25 <h2>Deployers: Configuring Storage</h2> 26 27 <p>The topmost object in Dejavu is a <tt>StorageManager</tt> object. 28 When building a Dejavu application, you must first create a 29 StorageManager instance, and must find a way to persist this object 30 across client connections. This can be achieved in multiple ways; 31 web applications, for example, will typically create a single process 32 to serve all requests. Desktop applications will probably create a 33 single StorageManager object for each running instance of the program.</p> 26 34 27 35 <p>Storage Managers insulate an application developer from the specifics of … … 32 40 application developer will have already prepared default config files 33 41 which you can simply "plug and play". But if you <i>need</i> more control 34 over your data storage, you have it, without becoming a programmer.</p> 35 36 37 <a name='configuration'><h3>Configuration Files</h3></a> 42 over your data storage, you have it.</p> 38 43 39 44 <p>When you deploy an app built with Dejavu, you must specify Storage 40 45 Managers to use for persisting application objects. This is usually 41 done through an ini-style configuration file. Here's a short example: 42 <pre>[Junct] 43 Class: access 44 Connect: "PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=D:\data\junct.mdb;" 46 done through an ini-style configuration file, although Dejavu itself 47 doesn't currently provide a parser for that. Here's a short example 48 of configuring a store in Python: 49 <pre> 50 from dejavu import storage 51 opts = {'connections.Connect': "PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=D:\data\junct.mdb;"} 52 root = storage.resolve("access", opts) 45 53 </pre> 46 The first line of our example ("[Junct]") names the Storage Manager; 47 each [section] in your conf file defines a different SM. You can use whatever 48 name you like here; in this example, we used the name of the application. 49 The second line tells Dejavu the <i>class</i> of SM we'd like to use. 54 The <tt class='def'>storage.resolve(store, options=None)</tt> call 55 tells Dejavu the <i>class</i> of SM we'd like to use. 50 56 For most applications, you'll decide which class to use based on the 51 57 database you want to use. Our example declares that we want to persist our 52 application data in an "MS Access" (i.e., Jet) database. The third line in 53 our example is a standard ADO Connect string. The MS Access class requires 54 this entry; other SM's may not.</p> 55 56 <h4>Common Configuration Entries</h4> 57 <p>There are a few configuration entries which (probably) apply to all 58 Storage Managers:</p> 59 60 <table> 61 <tr><th>Key</th><th>Example Value</th><th>Description</th></tr> 62 <tr> 63 <td>Class</td> 64 <td><tt>cache</tt>, or <tt>dejavu.storage.CachingProxy</tt></td> 65 <td>Which backend to use when instantiating this <tt>StorageManager</tt>. 66 You may supply a known short name or the full dotted-package name. 67 </td> 68 </tr> 69 <tr> 70 <td>Load Order</td> 71 <td><tt>5</tt></td> 72 <td>Optional. The order in which to load this SM. Lower numbers are 73 loaded first. SM's without a Load Order default to 0.</td> 74 </tr> 75 <tr> 76 <td>Shutdown Order</td> 77 <td><tt>10</tt></td> 78 <td>Optional. The order in which to shut down this SM. Lower numbers are 79 shut down first. SM's without a Shutdown Order default to 0.</td> 80 </tr> 81 <tr> 82 <td>Units</td> 83 <td><tt>[UnitCollection, UnitEngine, UnitEngineRule, FieldDashboardSumSet]</tt></td> 84 <td>Optional. Declares which Unit classes to manage with this SM 85 (see below).</td> 86 </tr> 87 </table> 88 89 <p>The "Units" entry is what you will use to separate application objects 90 into separate stores (if you need to). The objects in an application which 91 need to be stored are called "Units", and each Unit is of a certain Unit 92 class. If you specify a "Units" entry, then only Units of those classes 93 will be managed by that Storage Manager. If you do <i>not</i> specify such 94 an entry, then <b>all</b> Units will be handled by that Storage Manager. 95 This means that only <i>one</i> SM should be missing this entry.</p> 58 application data in an "MS Access" (i.e., Jet) database. You may supply a 59 known short name (like "sqlite") or the full dotted-package name. 60 <tt class='def'>storage.managers</tt> is a dict of short names to 61 full classes (or dotted class names). If you're including Dejavu in 62 a larger framework, feel free to add to this registry.</p> 63 64 <p>The options dict we pass in our example includes a standard ADO Connect 65 string. The MS Access class requires this entry; other SM's may not.</p> 96 66 97 67 … … 100 70 <h4>Microsoft SQL Server / Microsoft Access (Jet)</h4> 101 71 <p>This module was developed against ADO 2.7 and 2.8, 102 using MSDE, SQL Server 2000, and Access 2000. 103 Configuration entries:</p> 104 <ul> 105 <li><b>Class:</b> "sqlserver" (<tt>dejavu.storage.storeado.StorageManagerADO_SQLServer</tt>) 106 or "access" (<tt>dejavu.storage.storeado.StorageManagerADO_MSAccess</tt>)</li> 107 <li><b>Connect:</b> A valid ADO connect string. There are plenty of 108 online references for how to form these; for example, at 72 using MSDE, SQL Server 2000/2005, and Access 2000.</p> 73 74 <p>Classes:</p> 75 <ul> 76 <li>"sqlserver" (<tt>dejavu.storage.storeado.StorageManagerADO_SQLServer</tt>)</li> 77 <li>"[ms]access" (<tt>dejavu.storage.storeado.StorageManagerADO_MSAccess</tt>)</li> 78 </ul> 79 80 <p>Options:</p> 81 <ul> 82 <li><b>connections.Connect:</b> A valid ADO connect string. There are 83 plenty of online references for how to form these; for example, at 109 84 <a href='http://support.microsoft.com/?kbid=193332'>Microsoft</a>.</li> 110 85 </ul> 111 86 112 <h4>PostgreSQL (pyPgSQL)</h4>87 <h4>PostgreSQL</h4> 113 88 <p>This class was developed against 114 PostgreSQL 8.0.0 rc-1 on Win2k, 89 PostgreSQL 8.0.0 rc-1 (Win2k), 90 PostgreSQL 8.2.4 on i686-pc-mingw32 (Vista), 115 91 and also tested on 116 PostgreSQL 7.6.6-6 on Debian "sarge". 117 Configuration entries:</p> 118 <ul> 119 <li><b>Class:</b> "postgres" (<tt>dejavu.storage.storepypgsql.StorageManagerPgSQL</tt>)</li> 120 <li><b>Connect:</b> A connect string of the form "k=v k=v". For example, 121 <tt>"host=localhost dbname=myapp user=postgres password=hilar1ous"</tt>. 122 See the <a href='http://www.postgresql.org/docs/current/static/libpq.html'>libpq</a> 123 docs for complete information.</li> 124 </ul> 125 126 <h4>PostgreSQL (psycopg2)</h4> 127 <p>This class was developed against 128 PostgreSQL 8.0.0 rc-1 on Win2k, using psycopg2 version '2.0.5.1 (dec dt ext pq3)'. 129 Configuration entries:</p> 130 <ul> 131 <li><b>Class:</b> "psycopg" (<tt>dejavu.storage.storepsycopg.StorageManagerPsycoPg</tt>)</li> 132 <li><b>Connect:</b> A connect string of the form "k=v k=v". For example, 92 PostgreSQL 7.6.6-6 on Debian "sarge", 93 using 94 pyPgSQL-2.5.1 and psycopg2-2.0.6/2.0.5.1 95 </p> 96 97 <p>Classes:</p> 98 <ul> 99 <li>"postgres[ql]" or "pypgsql" (<tt>dejavu.storage.storepypgsql.StorageManagerPgSQL</tt>)</li> 100 <li>"psycopg[2]" (<tt>dejavu.storage.storepsycopg.StorageManagerPsycoPg</tt>)</li> 101 </ul> 102 103 <p>Options:</p> 104 <ul> 105 <li><b>connections.Connect:</b> A connect string of the form "k=v k=v". For example, 133 106 <tt>"host=localhost dbname=myapp user=postgres password=hilar1ous"</tt>. 134 107 See the <a href='http://www.postgresql.org/docs/current/static/libpq.html'>libpq</a> … … 140 113 mysql Ver 14.7 Distrib 4.1.8, for Win95/Win98 (i32), 141 114 and also tested on 142 mysql Ver 12.22 Distrib 4.0.23, for pc-linux-gnu (i386). 143 Configuration entries:</p> 144 <ul> 145 <li><b>Class:</b> "mysql" (<tt>dejavu.storage.storemysql.StorageManagerMySQL</tt>)</li> 115 mysql Ver 12.22 Distrib 4.0.23, for pc-linux-gnu (i386), and 116 5.0.45.community.nt (Vista) 117 </p> 118 119 <p>Classes:</p> 120 <ul> 121 <li>"mysql" (<tt>dejavu.storage.storemysql.StorageManagerMySQL</tt>)</li> 122 </ul> 123 124 <p>Options:</p> 125 <ul> 146 126 <li>Connection arguments: any of "host", "user", "passwd", "db", "port", 147 127 "unix_socket", "client_flag".<br />See the … … 155 135 sqlite 3.3.3 (pysqlite-1.1.7.win32-py2.4), 156 136 sqlite 2.8.15-3 on Debian "sarge", 157 and sqlite 3.3.4 (python 2.5 on win2k). 137 sqlite 3.3.4 (python 2.5 on win2k), 138 and sqlite 3.4.0 (pysqlite-2.3.5.win32-py2.4) on Vista. 158 139 If you have Python 2.5 or later, the builtin _sqlite3 library 159 will be used; otherwise, you need to install pysqlite 1.x. 160 Configuration entries:</p> 161 <ul> 162 <li><b>Class:</b> "sqlite" (<tt>dejavu.storage.storesqlite.StorageManagerSQLite</tt>)</li> 163 <li><b>Database:</b> Filename of the database. May be a relative path. 164 If the DB does not already exist, it will be created.</li> 140 will be used; otherwise, you need to install pysqlite. 141 </p> 142 143 <p>Classes:</p> 144 <ul> 145 <li>"sqlite" (<tt>dejavu.storage.storesqlite.StorageManagerSQLite</tt>)</li> 146 </ul> 147 148 <p>Options:</p> 149 <ul> 150 <li><b>Database:</b> Filename of the database. May be a relative path.</li> 165 151 <li><b>Mode:</b> Optional. DB file mode. Defaults to 0755.</li> 166 152 </ul> … … 168 154 169 155 <h4>Firebird (kinterbasdb)</h4> 170 <p>This class was developed against 171 KInterbasDB Version: (3, 2, 0, 'alpha', 1) and 172 Server Version: 'WI-V1.5.2.4731 Firebird 1.5' on Win2k. 173 Configuration entries:</p> 174 <ul> 175 <li><b>Class:</b> "firebird" (<tt>dejavu.storage.storefirebird.StorageManagerFirebird</tt>)</li> 156 <p>This class was developed against: 157 <ul> 158 <li>KInterbasDB Version: (3, 2, 0, 'alpha', 1) and 159 Server Version: 'WI-V1.5.2.4731 Firebird 1.5' on Win2k,</li> 160 <li>KInterbasDB Version: (3, 2, 0, 'final', 0) and 161 Server Version: 'WI-V2.0.3.12981 Firebird 2.0' on Vista.</li> 162 </ul> 163 </p> 164 165 <p>Classes:</p> 166 <ul> 167 <li>"firebird" (<tt>dejavu.storage.storefirebird.StorageManagerFirebird</tt>)</li> 168 </ul> 169 170 <p>Options:</p> 171 <ul> 176 172 <li><b>Name:</b> Filename of the database. Must be an absolute path.</li> 177 173 <li><b>Host:</b> The TCP host name, usually "localhost".</li> … … 191 187 <tr><th>Key</th><th>Example Value</th><th>Description</th></tr> 192 188 <tr> 193 <td>poolsize</td> 194 <td><tt>10</tt></td> 195 <td>Optional. Defaults to 10. If nonzero, connections will be pooled 196 (up to a total equal to <i>Pool Size</i>). If zero, no pool 197 will be used; each statement (!) will use a new connection.</td> 198 </tr> 199 <tr> 200 <td>Prefix</td> 189 <td>schemaclass.prefix</td> 201 190 <td><tt>myapp_</tt></td> 202 191 <td>Optional. If specified, all tables in the database will have names … … 206 195 </tr> 207 196 <tr> 208 <td>Type Adapter</td> 209 <td><tt>myapp.storage.FieldTypeAdapterForMyDB</tt></td> 210 <td>Optional. The "Type Adapter" is used to map Python types to database 211 column types for use in <tt>CREATE TABLE</tt> statements; for 212 example, the Python <tt>float</tt> type might be mapped to a 213 <tt>REAL</tt> column type. If you don't like the default column 214 types which your Storage Manager provides, you can write your own 215 adapter and declare its use here. The value should be the full 216 dotted package name of the class you wish to use.</td> 217 </tr> 218 <tr> 219 <td>To Adapter</td> 220 <td><tt>myapp.storage.AdapterToMyDBSQL</tt></td> 221 <td>Optional. The "To Adapter" is used to map Python values to database 222 values for use in SQL statements; for example, the Python <tt>str</tt> 223 type usually needs to be wrapped in quote marks. If you don't like 224 the SQL which your Storage Manager generates, you can write your 225 own adapter and declare its use here. The value should be the full 226 dotted package name of the class you wish to use.</td> 227 </tr> 228 <tr> 229 <td>From Adapter</td> 230 <td><tt>myapp.storage.AdapterFromMyDB</tt></td> 231 <td>Optional. The "From Adapter" is used to map incoming database values 232 (i.e., the results of a <tt>SELECT</tt> query) to Python values; for 233 example, your database may return a date value as a string, which 234 must then be converted to the Python <tt>datetime.date</tt> type. 235 If you don't like the default coercions which your Storage Manager 236 provides, you can write your own adapter and declare its use here. 237 The value should be the full dotted package name of the class you 238 wish to use.</td> 239 </tr> 240 <tr> 241 <td>default_isolation</td> 197 <td>connections.poolsize</td> 198 <td><tt>10</tt></td> 199 <td>Optional. Defaults to 10. If nonzero, connections will be pooled 200 (up to a total equal to <i>Pool Size</i>). If zero, no pool 201 will be used; each statement (!) will use a new connection.</td> 202 </tr> 203 <tr> 204 <td>connections.implicit_trans</td> 205 <td><tt>False</tt></td> 206 <td>Optional. Defaults to False. If True, a new connection will 207 automatically call "START TRANSACTION". It will also be associated 208 with the current thread, and any subsequent calls on the same thread 209 will then return the same connection object.</td> 210 </tr> 211 <tr> 212 <td>connections.contention</td> 213 <td><tt>'commit'</tt></td> 214 <td>Optional. If 'commit' (the default), schema-modifying commands 215 (e.g. add_property) will autocommit any pending transactions. 216 Change this to 'error' if you'd rather play it safe.</td> 217 </tr> 218 <tr> 219 <td>connections.default_isolation</td> 242 220 <td><tt>"READ COMMITTED"</tt></td> 243 221 <td>Optional. All database SM's already have a value for this, but you … … 254 232 <h4>RAM</h4> 255 233 <p>Persists Units in RAM; all Units are lost when the process exits.</p> 234 235 236 <h4>Memcached</h4> 237 238 <p><b>External Dependency: 239 <a href='http://www.tummy.com/Community/software/python-memcached/' 240 >python-memcached</a></b></p> 241 242 <p>Persists Units to a set of 243 <a href='http://www.danga.com/memcached/'>memcached</a> servers. 244 This is an extremely simple implementation; every value that is not 245 of type <tt>str</tt> or <tt>int</tt> is pickled. Querying will be slow-- 246 every Unit is sucked in one-by-one and tested in pure Python. 247 But for many cache applications, you don't need heavyweight query tools.</p> 248 249 <p>Classes:</p> 250 <ul><li>"memcache[d]" (<tt>dejavu.storage.storememcached.MemcachedStorageManager</tt>)</li></ul> 251 252 <p>Options:</p> 253 <ul> 254 <li><b>name:</b> Required. This string will be used to form namespaced 255 memcached keys.</li> 256 <li><b>memcached.servers:</b> Required. A list of strings of the form 257 'IP-address:port'. These will be passed directly into the 258 memcache.Client instance.</li> 259 <li><b>memcached.indexed:</b> if True (the default), this store will 260 maintain an index of all stored objects in memcached itself. This 261 is the 'safe' choice, and necessary if your only store is memcached. 262 If you run this store as an ObjectCache.cache, however, you should 263 turn this off, allowing ObjectCache.nextstore to maintain the 264 indexes--this allows the cache to run orders of magnitude faster.</li> 265 </ul> 266 267 268 <h4>JSON</h4> 269 270 <p><b>External Dependency: 271 <a href='http://undefined.org/python/#simplejson'>simplejson</a></b></p> 272 273 <p>Persists Units to a filesystem, one folder per class. Each folder 274 contains files, one per Unit, with the Unit identity as the file name. 275 Each of those unit files contains a JSON dict of Unit property values. 276 For example:</p> 277 278 <pre> 279 root/ 280 Album/ 281 | 78952.json 282 Song/ 283 1372.json 284 88.json 285 </pre> 286 287 <p>Querying will be slow--every Unit is sucked in one-by-one and tested in 288 pure Python. This is a good choice for test data or system tables--store 289 the data in JSON format for pretty version-control diffs, then migrate it 290 to another store when you run the tests or start the application.</p> 291 292 <p>Classes:</p> 293 <ul><li>"json" (<tt>dejavu.storage.storejson.StorageManagerJSON</tt>)</li></ul> 294 295 <p>Options:</p> 296 <ul> 297 <li><b>root:</b> Required. The file path (directory) in which to 298 place db files. Each Unit class will get its own subfolder, 299 of the same name as the class.</li> 300 <li><b>mode:</b> Optional. The mode arg to pass to <tt>os.mkdir</tt> 301 when creating folders. Defaults to '0777'.</li> 302 <li><b>idsepchar:</b> Optional. The character to use for separating 303 unit identities which are multivalent. Defaults to '_' (underscore). 304 For example, a Unit with <tt>identifiers = ('Name', 'DOB')</tt> 305 would get a folder name like 'Fred_20040321'.</li> 306 <li><b>encoding:</b> Passed to the simplejson.Decoder.</li> 307 <li><b>skipkeys:</b> Passed to the simplejson.Encoder. 308 Defaults to False.</li> 309 <li><b>check_circular:</b> Passed to the simplejson.Encoder. 310 Defaults to True.</li> 311 <li><b>allow_nan:</b> Passed to the simplejson.Encoder. 312 Defaults to False.</li> 313 <li><b>indent:</b> Passed to the simplejson.Encoder. 314 Defaults to None.</li> 315 </ul> 316 256 317 257 318 <h4>Shelve</h4> … … 273 334 is called on program exit.</p> 274 335 275 Configuration entries:</p> 276 <ul> 277 <li><b>Class:</b> "shelve" 278 (<tt>dejavu.storage.storeshelve.StorageManagerShelve</tt>)</li> 336 <p>Classes:</p> 337 <ul><li>"shelve" (<tt>dejavu.storage.storeshelve.StorageManagerShelve</tt>)</li></ul> 338 339 <p>Options:</p> 340 <ul> 279 341 <li><b>Path:</b> The file path (directory) in which to place db files. 280 342 Each Unit subclass will get its own file, of the same name as the … … 310 372 for example, an upload site may only need files looked up by ID.</p> 311 373 312 Configuration entries:</p> 313 <ul> 314 <li><b>Class:</b> "folders" 315 (<tt>dejavu.storage.storeshelve.StorageManagerShelve</tt>)</li> 374 <p>Classes:</p> 375 <ul><li>"folders" (<tt>dejavu.storage.storeshelve.StorageManagerShelve</tt>)</li></ul> 376 377 <p>Options:</p> 378 <ul> 316 379 <li><b>root:</b> Required. The file path (directory) in which to 317 380 place db files. Each Unit class will get its own subfolder, … … 336 399 <p>Some Storage Managers act as "middleware", and can be chained together 337 400 to provide layered functionality. Consider, for example, the 338 <tt> CachingProxy</tt> class; it has another Storage Manager401 <tt>ObjectCache</tt> class; it has another Storage Manager 339 402 "behind it", which it proxies. It can be used to cache objects between 340 403 client connections independently from the underlying, database-specific 341 404 Storage Manager. The beauty of this design is that the decision to 342 use a CachingProxyis completely up to the deployer, <i>not</i> the405 use a ObjectCache is completely up to the deployer, <i>not</i> the 343 406 application developer. The deployer can separate stores, test response 344 407 times, and address other integration concerns on their own systems.</p> 345 408 346 <h4> Caching Proxy</h4>409 <h4>Object Cache</h4> 347 410 <p>Use this class to persist Units in memory between client connections. 348 It must proxy another Storage Manager. Configuration entries:</p> 349 <ul> 350 <li><b>Class:</b> <tt>dejavu.storage.CachingProxy</tt></li> 351 <li><b>Next Store:</b> Required. The name of the next Storage Manager 352 in the chain.</li> 411 It must proxy another Storage Manager.</p> 412 413 <p>Classes:</p> 414 <ul><li>"cache" (<tt>dejavu.storage.caching.ObjectCache</tt>)</li></ul> 415 416 <p>Options:</p> 417 <ul> 418 <li><b>Next Store:</b> Required. The next Storage Manager in the chain.</li> 419 <li><b>cache:</b> Optional. The Storage Manager to use for the cache. 420 If not given, it defaults to a RAM store.</li> 421 </ul> 422 423 <h4>Aged Cache</h4> 424 <p>Use this class to persist Units in memory between client connections. 425 It must proxy another Storage Manager.</p> 426 427 <p>Classes:</p> 428 <ul><li>"aged" (<tt>dejavu.storage.caching.AgedCache</tt>)</li></ul> 429 430 <p>Options:</p> 431 <ul> 432 <li><b>Next Store:</b> Required. The next Storage Manager in the chain.</li> 433 <li><b>cache:</b> Optional. The Storage Manager to use for the cache. 434 If not given, it defaults to a RAM store.</li> 353 435 <li><b>Lifetime:</b> Optional. The recurrence string which declares 354 436 how often to sweep Units out of the in-memory cache. The string you … … 370 452 371 453 372 <h4>Burned Proxy</h4>454 <h4>Burned Cache</h4> 373 455 <p>Use this class to persist Units in memory between client connections. 374 It needs another Storage Manager to proxy. Unlike the Caching Proxyabove,456 It needs another Storage Manager to proxy. Unlike the ObjectCache above, 375 457 this Storage Manager recalls all Units at once upon the first request, 376 458 and won't recall them again from storage. They are "burned" into memory 377 for the lifetime of the application. Configuration entries:</p> 378 <ul> 379 <li><b>Class:</b> <tt>dejavu.storage.BurnedProxy</tt></li> 380 <li><b>Next Store:</b> Required. The name of the next Storage Manager 381 in the chain.</li> 382 <li><b>Lifetime:</b> Optional. The recurrence string which declares 383 how often to sweep Units out of the in-memory cache. See the 384 Caching Proxy, above, for recurrence string formats. In general, 385 you should <b>not</b> set this value for BurnedProxy stores.</li> 386 </ul> 387 459 for the lifetime of the application.</p> 460 461 <p>Classes:</p> 462 <ul><li>"burned" (<tt>dejavu.storage.caching.BurnedCache</tt>)</li></ul> 463 464 <p>Options:</p> 465 <ul> 466 <li><b>Next Store:</b> Required. The next Storage Manager in the chain.</li> 467 <li><b>cache:</b> Optional. The Storage Manager to use for the cache. 468 If not given, it defaults to a RAM store.</li> 469 </ul> 470 471 <a name='partitioning'><h3>Partitioning</h3></a> 472 473 <h4>Vertical Partitioner</h4> 474 <p>This class replaces the old Arena object from Dejavu 1.x. It allows you 475 to aggregate multiple stores into a single interface, partitioned by Unit 476 class. Unlike most other StorageManagers, it takes no options. Instead, 477 you will generally set this as the root of your storage graph and 478 repeatedly call its <tt class='def'>add_store(name, store)</tt> method. 479 There's also a corresponding <tt class='def'>remove_store(name)</tt> 480 method.</p> 481 482 <p>Once you've added stores, the <tt class='def'>stores</tt> attribute is a 483 dict from store names to StorageManager instances. However, you shouldn't 484 manipulate this directly--use add/remove_store instead. When you call 485 add_store, it will also set up the partitioner's <tt class='def'>classmap</tt> 486 attribute, which is used to direct queries and other command to the correct 487 store(s) based on the class. DDL methods will generally dispatch to all 488 stores for each class. DML methods will generally dispatch to 489 <tt>classmap[unit.__class__][0]</tt>; those which involve multiple 490 classes (e.g. multirecall), will try to find a single store which 491 handles all classes in the given relation. To override this default 492 search, you can add entries to <tt>classmap</tt> of the form: 493 <tt>{(clsA, clsB, clsC): [store1]}</tt>, which instructs the 494 partitioner to use the given store for any Join with the same 495 order, such as <tt>(clsA << clsB) & clsC</tt>.</p> 496 497 <p>Classes:</p> 498 <ul><li><tt>dejavu.storage.partitions.VerticalPartitioner</tt></li></ul> 499 500 <p>Options:</p> 501 <ul> 502 <li>None</li> 503 </ul> 388 504 389 505 <a name='comparison'><h3>SM Comparison Chart</h3></a> … … 420 536 <th>mysql</th> 421 537 <th>postgres</th> 422 <th>ram</th>423 <th>shelve</th>424 538 <th>sqlite</th> 425 539 <th>sqlserver</th> 540 <th>ram</th> 541 <th>memcached</th> 542 <th>shelve</th> 426 543 <th>folders</th> 544 <th>json</th> 427 545 </tr> 428 546 … … 433 551 <td class='python'>P</td> 434 552 <td class='python'>P</td> 435 <td class='notsup'>N</td> 436 <td class='notsup'>N</td> 437 <td class='python'>P</td> 438 <td class='python'>P</td> 553 <td class='python'>P</td> 554 <td class='python'>P</td> 555 <td class='notsup'>N</td> 556 <td class='notsup'>N</td> 557 <td class='notsup'>N</td> 558 <td class='notsup'>N</td> 439 559 <td class='notsup'>N</td> 440 560 </tr> … … 446 566 <td>Y</td> 447 567 <td>Y</td> 448 <td class='notsup'>N</td> 449 <td class='notsup'>N</td> 450 <td>Y</td> 451 <td>Y</td> 568 <td>Y</td> 569 <td>Y</td> 570 <td class='notsup'>N</td> 571 <td class='notsup'>N</td> 572 <td class='notsup'>N</td> 573 <td class='notsup'>N</td> 452 574 <td class='notsup'>N</td> 453 575 </tr> … … 459 581 <td>Y</td> 460 582 <td>Y</td> 461 <td class='notsup'>N</td> 462 <td class='notsup'>N</td> 463 <td>Y</td> 464 <td>Y</td> 583 <td>Y</td> 584 <td>Y</td> 585 <td class='notsup'>N</td> 586 <td class='notsup'>N</td> 587 <td class='notsup'>N</td> 588 <td class='notsup'>N</td> 465 589 <td class='notsup'>N</td> 466 590 </tr> … … 472 596 <td>64</td> 473 597 <td>63</td> 474 <td class='python'>P</td>475 <td class='python'>P</td>476 598 <td>no limit?</td> 477 599 <td>128</td> 600 <td class='python'>P</td> 601 <td class='python'>P</td> 602 <td class='python'>P</td> 478 603 <td>OS-dependent</td> 604 <td>OS-dependent</td> 479 605 </tr> 480 606 … … 489 615 <td>Y</td> 490 616 <td>Y</td> 617 <td>Y</td> 491 618 <td>Y <a href='#filenames'>[3]</a></td> 619 <td>Y <a href='#filenames'>[3]</a></td> 492 620 </tr> 493 621 … … 498 626 <td>Y</td> 499 627 <td>Y</td> 500 <td class='python'>P</td> 501 <td class='python'>P</td> 502 <td>Y</td> 628 <td>Y</td> 629 <td class='python'>P</td> 630 <td class='python'>P</td> 631 <td class='python'>P</td> 632 <td class='python'>P</td> 503 633 <td class='python'>P</td> 504 634 <td class='python'>P</td> … … 511 641 <td>Y</td> 512 642 <td>Y</td> 513 <td class='python'>P</td>514 <td class='python'>P</td>515 643 <td>Y</td> 516 644 <td><tt><, <=, ==, !=, >, >=</tt></td> 517 645 <td class='python'>P</td> 646 <td class='python'>P</td> 647 <td class='python'>P</td> 648 <td class='python'>P</td> 649 <td class='python'>P</td> 518 650 </tr> 519 651 … … 524 656 <td>Y</td> 525 657 <td>Y</td> 526 <td class='python'>P</td>527 <td class='python'>P</td>528 658 <td>3.0.8+</td> 529 659 <td>Y</td> 530 660 <td class='python'>P</td> 661 <td class='python'>P</td> 662 <td class='python'>P</td> 663 <td class='python'>P</td> 664 <td class='python'>P</td> 531 665 </tr> 532 666 … … 537 671 <td>Y</td> 538 672 <td>Y</td> 539 <td class='python'>P</td>540 <td class='python'>P</td>541 673 <td>3.1.0+</td> 542 674 <td>Y</td> 543 675 <td class='python'>P</td> 676 <td class='python'>P</td> 677 <td class='python'>P</td> 678 <td class='python'>P</td> 679 <td class='python'>P</td> 544 680 </tr> 545 681 … … 550 686 <td>Y</td> 551 687 <td>Y</td> 552 <td>Y</td>553 <td>Y</td>554 688 <td class='python'>P <a href='#sqlite-alter-table'>[2]</a><br />(add: 3.2.0+)</td> 689 <td>Y</td> 690 <td>Y</td> 691 <td>Y</td> 692 <td>Y</td> 555 693 <td>Y</td> 556 694 <td>Y</td> … … 563 701 <th>mysql</th> 564 702 <th>postgres</th> 565 <th>ram</th>566 <th>shelve</th>567 703 <th>sqlite</th> 568 704 <th>sqlserver</th> 705 <th>ram</th> 706 <th>memcached</th> 707 <th>shelve</th> 569 708 <th>folders</th> 709 <th>json</th> 570 710 </tr> 571 711 … … 576 716 <td>16</td> 577 717 <td>1000</td> 578 <td class='python'>P (pickle)</td>579 <td class='python'>P (pickle)</td>580 718 <td>0 (always uses TEXT instead)</td> 581 719 <td>12</td> 582 720 <td class='python'>P (pickle)</td> 721 <td class='python'>P (pickle)</td> 722 <td class='python'>P (pickle)</td> 723 <td class='python'>P (pickle)</td> 724 <td>Python limit?</td> 583 725 </tr> 584 726 … … 589 731 <td>8000 (row limit)</td> 590 732 <td>1 GB?</td> 591 <td class='python'>P (pickle)</td>592 <td class='python'>P (pickle)</td>593 733 <td>1 MB (row limit)</td> 594 734 <td>8000 <a href='#ntext-bytes'>[4]</a></td> 595 735 <td class='python'>P (pickle)</td> 736 <td>1 MB (object limit, adjustable)</td> 737 <td class='python'>P (pickle)</td> 738 <td class='python'>P (pickle)</td> 739 <td>Python limit?</td> 596 740 </tr> 597 741 … … 602 746 <td>1000-01-01 00:00:00 to 9999-12-31 23:59:59</td> 603 747 <td>4713 BC to 5874897 AD</td> 604 <td class='python'>P</td>605 <td class='python'>P</td>606 748 <td>4714-11-24 BC to ???</td> 607 749 <td>1753-01-01 00:00:00.0 to 9999-12-31 23:59:59.997</td> 750 <td class='python'>P</td> 751 <td class='python'>P</td> 752 <td class='python'>P</td> 753 <td class='python'>P</td> 608 754 <td class='python'>P</td> 609 755 </tr> … … 615 761 <td>1 second</td> 616 762 <td>1 microsecond</td> 617 <td class='python'>P</td>618 <td class='python'>P</td>619 763 <td>1 second</td> 620 764 <td>1 second</td> 621 765 <td class='python'>P</td> 766 <td class='python'>P</td> 767 <td class='python'>P</td> 768 <td class='python'>P</td> 769 <td>1 second</td> 622 770 </tr> 623 771 … … 628 776 <td>Y</td> 629 777 <td>Y</td> 630 <td class='python'>P</td>631 <td class='python'>P</td>632 778 <td>3.2.3+ <a href='#perfect-dates'>[1]</a></td> 633 779 <td>Y</td> 634 780 <td class='python'>P</td> 781 <td class='python'>P</td> 782 <td class='python'>P</td> 783 <td class='python'>P</td> 784 <td class='python'>P</td> 635 785 </tr> 636 786 … … 641 791 <td>Y</td> 642 792 <td>Y</td> 643 <td class='python'>P</td>644 <td class='python'>P</td>645 793 <td>3.2.3+ <a href='#perfect-dates'>[1]</a></td> 646 794 <td>Y</td> 795 <td class='python'>P</td> 796 <td class='python'>P</td> 797 <td class='python'>P</td> 798 <td class='python'>P</td> 647 799 <td class='python'>P</td> 648 800 </tr> … … 656 808 <td>Y</td> 657 809 <td>Y</td> 658 <td class='python'>P</td> 659 <td class='python'>P</td> 660 <td>Y</td> 661 <td>Y</td> 810 <td>Y</td> 811 <td>Y</td> 812 <td class='python'>P</td> 813 <td class='python'>P</td> 814 <td class='python'>P</td> 815 <td class='python'>P</td> 662 816 <td class='python'>P</td> 663 817 </tr> … … 669 823 <td>Y</td> 670 824 <td>Y</td> 671 <td class='python'>P</td> 672 <td class='python'>P</td> 673 <td>Y</td> 674 <td>Y</td> 825 <td>Y</td> 826 <td>Y</td> 827 <td class='python'>P</td> 828 <td class='python'>P</td> 829 <td class='python'>P</td> 830 <td class='python'>P</td> 675 831 <td class='python'>P</td> 676 832 </tr> … … 682 838 <th>mysql</th> 683 839 <th>postgres</th> 684 <th>ram</th>685 <th>shelve</th>686 840 <th>sqlite</th> 687 841 <th>sqlserver</th> 842 <th>ram</th> 843 <th>memcached</th> 844 <th>shelve</th> 688 845 <th>folders</th> 846 <th>json</th> 689 847 </tr> 690 848 … … 695 853 <td>Y</td> 696 854 <td class='notsup'>N <a href='#too-isolated'>[7]</a></td> 697 <td></td> 698 <td></td> 699 <td class='notsup'>N</td> 700 <td>Y</td> 855 <td class='notsup'>N</td> 856 <td>Y</td> 857 <td></td> 858 <td></td> 859 <td></td> 860 <td></td> 701 861 <td></td> 702 862 </tr> … … 708 868 <td>Y</td> 709 869 <td>Y</td> 710 <td></td>711 <td></td>712 870 <td class='notsup'>N</td> 713 871 <td>Y (timeout)</td> 872 <td></td> 873 <td></td> 874 <td></td> 875 <td></td> 714 876 <td></td> 715 877 </tr> … … 720 882 <td class='notsup'>N <a href='#too-isolated'>[7]</a></td> 721 883 <td class='notsup'>N <a href='#too-isolated'>[7]</a></td> 722 <td></td>723 <td></td>724 884 <td class='notsup'>N</td> 725 885 <td>Y (timeout)</td> 726 886 <td></td> 887 <td></td> 888 <td></td> 889 <td></td> 890 <td></td> 727 891 </tr> 728 892 <tr> … … 732 896 <td>Y (timeout)</td> 733 897 <td>Y</td> 734 <td></td>735 <td></td>736 898 <td>Y <a href='#memory-trans'>[8]</a></td> 737 899 <td>Y (timeout)</td> 738 900 <td></td> 901 <td></td> 902 <td></td> 903 <td></td> 904 <td></td> 739 905 </tr> 740 906 <tr> … … 744 910 <td>Y</td> 745 911 <td>Y</td> 746 <td></td> 747 <td></td> 748 <td class='notsup'>N</td> 749 <td>Y</td> 912 <td class='notsup'>N</td> 913 <td>Y</td> 914 <td></td> 915 <td></td> 916 <td></td> 917 <td></td> 750 918 <td></td> 751 919 </tr> trunk/dejavu/engines.py
r542 r543 324 324 def snapshots(self): 325 325 """Unit Collections obtained by executing the rules sometime in the past.""" 326 allSnap = self.sandbox.recall(UnitCollection, EngineID=self.ID)326 allSnap = self.sandbox.recall(UnitCollection, dict(EngineID=self.ID)) 327 327 allSnap.sort(dejavu.sort(u'Timestamp')) 328 328 return allSnap trunk/dejavu/sandboxes.py
r542 r543 68 68 for arg, key in zip(args, uniq): 69 69 kwargs[str(key)] = arg 70 return self.unit(cls, logic.filter(**kwargs))70 return self.unit(cls, **kwargs) 71 71 recaller.__doc__ = "A single %s Unit, else None." % name 72 72 return recaller … … 117 117 unit.sandbox = None 118 118 119 def xrecall(self, classes, expr=None, inherit=False, **kwargs):119 def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): 120 120 """Iterator over units of the given class(es) which match expr. 121 122 If inherit is True, units of the given class and all registered123 subclasses of the given class will be recalled.124 121 125 122 If the 'classes' arg is a UnitJoin, each yielded value will … … 142 139 """ 143 140 if isinstance(classes, dejavu.UnitJoin): 144 for unitrow in self._xmultirecall(classes, expr, **kwargs): 141 for unitrow in self._xmultirecall(classes, expr, order=order, 142 limit=limit, offset=offset): 145 143 yield unitrow 146 144 return 147 145 148 146 cls = classes 149 expr = logic.combine(expr, kwargs) 150 151 if inherit: 152 # Collect all registered subclasses of cls. 153 # Note that cls is a subclass of itself. 154 classes = [c for c in self.store.classes if issubclass(c, cls)] 155 else: 156 classes = [cls] 157 if not classes: 158 # Even the requested class is not registered. 159 raise KeyError("The '%s' class is not registered." % cls.__name__) 160 161 for cls in classes: 162 cache = self._cache(cls) 163 164 # Special-case the scenario where one Unit is expected 165 # and called by ID. We should be able to save a database hit. 166 if expr: 167 fc = expr.func.func_code 168 if (fc.co_code == _simple_attr_compare and 147 if not isinstance(expr, logic.Expression): 148 expr = logic.Expression(expr) 149 150 cache = self._cache(cls) 151 152 # Special-case the scenario where one Unit is expected 153 # and called by ID. We should be able to save a database hit. 154 if expr: 155 fc = expr.func.func_code 156 if (fc.co_code == _simple_attr_compare and 169 157 cls.identifiers == (fc.co_names[-1], )): 170 ID = fc.co_consts[-1] 171 unit = cache.get((ID,)) 172 if unit is not None: 173 # Do NOT call on_recall here. That should be called 174 # only at the Sandbox-SM boundary. 175 yield unit 176 return 177 178 # Query the cache. We have to use a static copy of the 179 # keys, to ensure that our cache doesn't change size 180 # during iteration (due to overlapping xrecalls). 181 keys = cache.keys() 182 for id in keys: 183 unit = cache.get(id) 184 if unit and ((expr is None) or expr.evaluate(unit)): 158 ID = fc.co_consts[-1] 159 unit = cache.get((ID,)) 160 if unit is not None: 185 161 # Do NOT call on_recall here. That should be called 186 162 # only at the Sandbox-SM boundary. 187 163 yield unit 188 189 # Query Storage. 190 if not cls.identifiers: 191 # Classes with no identifiers cannot be compared to our cache 192 for unit in self.store.xrecall(cls, expr): 193 unit.sandbox = self 194 if hasattr(unit, 'on_recall'): 195 try: 196 unit.on_recall() 197 except errors.UnrecallableError: 198 continue 199 yield unit 200 return 201 202 for unit in self.store.xrecall(cls, expr): 164 return 165 166 # Query the cache. We have to use a static copy of the 167 # keys, to ensure that our cache doesn't change size 168 # during iteration (due to overlapping xrecalls). 169 keys = cache.keys() 170 for id in keys: 171 unit = cache.get(id) 172 if unit and ((expr is None) or expr.evaluate(unit)): 173 # Do NOT call on_recall here. That should be called 174 # only at the Sandbox-SM boundary. 175 yield unit 176 177 # Query Storage. 178 if not cls.identifiers: 179 # Classes with no identifiers cannot be compared to our cache 180 for unit in self.store.xrecall(cls, expr, order=order, 181 limit=limit, offset=offset): 182 unit.sandbox = self 183 if hasattr(unit, 'on_recall'): 184 try: 185 unit.on_recall() 186 except errors.UnrecallableError: 187 continue 188 yield unit 189 else: 190 for unit in self.store.xrecall(cls, expr, order=order, 191 limit=limit, offset=offset): 203 192 id = unit.identity() 204 193 # Don't offer up a unit that was already checked in our cache … … 223 212 yield unit 224 213 225 def recall(self, classes, expr=None, inherit=False, **kwargs):214 def recall(self, classes, expr=None, order=None, limit=None, offset=None): 226 215 """List of units of the given class(es) which match expr. 227 228 If inherit is True, units of the given class and all registered229 subclasses of the given class will be recalled.230 216 231 217 If the 'classes' arg is a UnitJoin, each yielded value will … … 247 233 multiple classes. 248 234 """ 249 return [x for x in self.xrecall(classes, expr, inherit, **kwargs)] 250 251 def unit(self, classes, expr=None, **kwargs): 252 """A single Unit/Unit set which matches the given expr, else None. 253 254 **kwargs will be combined into an Expression via logic.filter. 255 256 The first Unit (or set of Units, if 'classes' is a UnitJoin) 257 matching the expression is returned; if no Units match, None 258 is returned. 259 """ 260 if isinstance(classes, dejavu.UnitJoin): 261 try: 262 return self._xmultirecall(classes, expr, **kwargs).next() 263 except StopIteration: 264 return None 265 266 cls = classes 235 return [x for x in self.xrecall(classes, expr, order=order, 236 limit=limit, offset=offset)] 237 238 def unit(self, cls, **kwargs): 239 """A single Unit which matches the given kwargs, else None. 240 241 The first Unit matching the kwargs is returned; if no Units match, 242 None is returned. 243 """ 267 244 cache = self._cache(cls) 268 fullexpr = logic.combine(expr, kwargs)269 245 270 246 # Special-case the scenario where one Unit is expected 271 247 # and called by ID. We should be able to save a database hit. 272 ident = None 273 if expr: 274 fc = fullexpr.func.func_code 275 # TODO: handle multiple identifiers 276 if (fc.co_code == _simple_attr_compare and 277 cls.identifiers == (fc.co_names[-1], )): 278 ident = (fc.co_consts[-1],) 279 elif set(kwargs.keys()) == set(cls.identifiers): 248 if set(kwargs.keys()) == set(cls.identifiers): 280 249 ident = tuple([kwargs[k] for k in cls.identifiers]) 281 if ident:282 unit= cache.get(ident)283 if unitis None:284 unit = self.store.unit(cls, expr=expr, **kwargs)285 if unitis not None:286 unit.sandbox = self287 cache[unit.identity()] = unit288 if hasattr(unit, 'on_recall'):289 try:290 unit.on_recall()291 except errors.UnrecallableError:292 return None293 return unit250 if ident: 251 u = cache.get(ident) 252 if u is None: 253 u = self.store.unit(cls, **kwargs) 254 if u is not None: 255 u.sandbox = self 256 cache[ident] = u 257 if hasattr(u, 'on_recall'): 258 try: 259 u.on_recall() 260 except errors.UnrecallableError: 261 return None 262 return u 294 263 295 264 # Query the cache. We have to use a static copy of the … … 298 267 keys = cache.keys() 299 268 for id in keys: 300 unit = cache.get(id) 301 if unit and ((fullexpr is None) or fullexpr.evaluate(unit)): 302 # Do NOT call on_recall here. That should be called 303 # only at the Sandbox-SM boundary. 304 return unit 269 u = cache.get(id) 270 if u: 271 for k, v in kwargs.iteritems(): 272 if getattr(u, k) != v: 273 break 274 else: 275 # Do NOT call on_recall here. That should be called 276 # only at the Sandbox-SM boundary. 277 return u 305 278 306 279 # Query Storage. 307 u nit = self.store.unit(cls, expr=expr, **kwargs)308 if u nitis not None:309 u nit.sandbox = self280 u = self.store.unit(cls, **kwargs) 281 if u is not None: 282 u.sandbox = self 310 283 311 284 # Classes with no identifiers cannot be compared to our cache … … 315 288 # (even between our cache lookups and this lookup). 316 289 # Make sure the cache lookup and get happens atomically. 317 id = u nit.identity()290 id = u.identity() 318 291 existing = cache.get(id) 319 292 if existing: 320 293 return existing 321 cache[id] = u nit322 323 if hasattr(u nit, 'on_recall'):294 cache[id] = u 295 296 if hasattr(u, 'on_recall'): 324 297 try: 325 u nit.on_recall()298 u.on_recall() 326 299 except errors.UnrecallableError: 327 300 return None 328 return unit 329 330 def _xmultirecall(self, classes, expr=None, **kwargs): 301 return u 302 303 def _xmultirecall(self, classes, expr=None, 304 order=None, limit=None, offset=None): 331 305 """Recall units of each cls if they together match the expr. 332 306 … … 349 323 multiple classes. 350 324 """ 351 # Do this even if there are no kwargs, because it will also 352 # convert a passed lambda into an Expression object. 353 expr = logic.combine(expr, kwargs) 325 if not isinstance(expr, logic.Expression): 326 expr = logic.Expression(expr) 354 327 355 328 # This is broken. If a filter expr is supplied, then the store may … … 357 330 # in the resultset. If you're using xmulti with no expr's, or 358 331 # in read-only scripts, it should be OK for now. But if you mutate 359 # Units and then call _multirecall, expect inconsistent results. 360 for unitset in self.store._xmultirecall(classes, expr): 332 # Units and then call _xmultirecall, expect inconsistent results. 333 for unitset in self.store._xmultirecall(classes, expr, order=order, 334 limit=limit, offset=offset): 361 335 confirmed = True 362 336 for index in xrange(len(unitset)): 363 337 unit = unitset[index] 364 338 id = unit.identity() 339 if not unit.sequencer.valid_id(id): 340 # This is a 'dummy unit' from an outer join. 341 continue 365 342 cache = self._cache(unit.__class__) 366 343 if id in cache: … … 453 430 return [x for x in self.xview(query, distinct=distinct)] 454 431 455 def sum(self, cls, attr, expr=None , **kwargs):432 def sum(self, cls, attr, expr=None): 456 433 """Sum of all non-None values for the given cls.attr.""" 457 434 expr = logic.Expression(lambda x: getattr(x, attr) != None) + expr 458 435 return sum([row[0] for row in 459 self.view(dejavu.Query(cls, (attr,), expr , **kwargs))])436 self.view(dejavu.Query(cls, (attr,), expr))]) 460 437 461 438 def count(self, cls, expr=None): … … 467 444 return len(self.view((cls, uniq, expr), distinct=True)) 468 445 469 def range(self, cls, attr, expr=None , **kwargs):446 def range(self, cls, attr, expr=None): 470 447 """Distinct, non-None attr values (ordered and continuous, if possible). 471 448 … … 482 459 (sorted, if possible). 483 460 """ 484 query = dejavu.Query(cls, [attr], expr , **kwargs)461 query = dejavu.Query(cls, [attr], expr) 485 462 existing = [x[0] for x in self.view(query, distinct=True) 486 463 if x is not None] trunk/dejavu/storage/__init__.py
r542 r543 255 255 limit=limit, offset=offset)] 256 256 257 def unit(self, classes, expr=None, **kwargs): 258 """A single Unit/Unit set which matches the given expr, else None. 259 260 **kwargs will be combined into an Expression via logic.filter. 261 262 The first Unit (or set of Units, if 'classes' is a UnitJoin) 263 matching the expression is returned; if no Units match, None 264 is returned. 265 """ 266 expr = logic.combine(expr, kwargs) 267 257 def unit(self, cls, **kwargs): 258 """A single Unit which matches the given kwargs, else None. 259 260 The first Unit matching the kwargs is returned; if no Units match, 261 None is returned. 262 """ 268 263 try: 269 if isinstance(classes, dejavu.UnitJoin): 270 return self._xmultirecall(classes, expr).next() 271 else: 272 return self.xrecall(classes, expr).next() 264 return self.xrecall(cls, logic.filter(**kwargs), limit=1).next() 273 265 except StopIteration: 274 266 return None … … 438 430 def _xmultirecall(self, classes, expr=None, order=None, limit=None, offset=None): 439 431 """Yield lists of units of the given classes which match expr.""" 440 if expr is None:441 expr = logic.Expression( lambda *args: True)432 if not isinstance(expr, logic.Expression): 433 expr = logic.Expression(expr) 442 434 443 435 # TODO: deconstruct expr into a set of subexpr's, one for … … 573 565 return len(self.view((cls, uniq, expr), distinct=True)) 574 566 575 def range(self, cls, attr, expr=None , **kwargs):567 def range(self, cls, attr, expr=None): 576 568 """Distinct, non-None attr values (ordered and continuous, if possible). 577 569 … … 588 580 (sorted, if possible). 589 581 """ 590 query = dejavu.Query(cls, [attr], expr , **kwargs)582 query = dejavu.Query(cls, [attr], expr) 591 583 existing = [x[0] for x in self.xview(query, distinct=True) 592 584 if x is not None] … … 611 603 612 604 return existing 605 606 def sum(self, cls, attr, expr=None): 607 """Sum of all non-None values for the given cls.attr.""" 608 expr = logic.Expression(lambda x: getattr(x, attr) != None) + expr 609 return sum([row[0] for row in 610 self.xview(dejavu.Query(cls, (attr,), expr))]) 613 611 614 612 # Transactions # trunk/dejavu/storage/caching.py
r542 r543 42 42 self.cache = resolve("ram") 43 43 44 def unit(self, classes, expr=None, **kwargs): 45 """A single Unit/Unit set which matches the given expr, else None. 46 47 **kwargs will be combined into an Expression via logic.filter. 48 49 The first Unit (or set of Units, if 'classes' is a UnitJoin) 50 matching the expression is returned; if no Units match, None 51 is returned. 52 """ 53 if isinstance(classes, dejavu.UnitJoin): 54 if set(classes).issubset(self.cache.classes): 55 u = self.cache.unit(classes, expr=expr, **kwargs) 56 if u is not None: 57 return u 58 59 unitrow = self.nextstore.unit(classes, expr=expr, **kwargs) 60 if unitrow is not None: 61 for u in unitrow: 62 self.cache.save(u, forceSave=True) 63 return unitrow 64 else: 65 cls = classes 66 if cls in self.cache.classes: 67 u = self.cache.unit(cls, expr=expr, **kwargs) 68 if u is not None: 69 return u 70 71 u = self.nextstore.unit(cls, expr=expr, **kwargs) 44 def unit(self, cls, **kwargs): 45 """A single Unit which matches the given kwargs, else None. 46 47 The first Unit matching the kwargs is returned; if no Units match, 48 None is returned. 49 """ 50 if cls in self.cache.classes: 51 u = self.cache.unit(cls, **kwargs) 72 52 if u is not None: 73 self.cache.save(u, forceSave=True) 74 return u 53 return u 54 55 u = self.nextstore.unit(cls, **kwargs) 56 if u is not None: 57 self.cache.save(u, forceSave=True) 58 59 return u 75 60 76 61 def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): trunk/dejavu/storage/storefs.py
r542 r543 98 98 return unitdict 99 99 100 def unit(self, classes, expr=None, **kwargs): 101 """A single Unit/Unit set which matches the given expr, else None. 102 103 **kwargs will be combined into an Expression via logic.filter. 104 105 The first Unit (or set of Units, if 'classes' is a UnitJoin) 106 matching the expression is returned; if no Units match, None 107 is returned. 108 """ 109 if isinstance(classes, dejavu.UnitJoin): 110 try: 111 return self._xmultirecall(classes, expr, order=order, 112 limit=limit, offset=offset).next() 113 except StopIteration: 114 return None 115 116 cls = classes 117 if (expr is None) and set(kwargs.keys()) == set(cls.identifiers): 100 def unit(self, cls, **kwargs): 101 """A single Unit which matches the given kwargs, else None. 102 103 The first Unit matching the kwargs is returned; if no Units match, 104 None is returned. 105 """ 106 if set(kwargs.keys()) == set(cls.identifiers): 118 107 # Looking up a Unit by its identifiers. 119 108 # Skip walking the shelf. 120 109 if self.logflags & logflags.RECALL: 121 expr = logic.combine(expr, kwargs) 122 self.log(logflags.RECALL.message(cls, expr)) 110 self.log(logflags.RECALL.message(cls, kwargs)) 123 111 124 112 classdir = self.shelf(cls) 125 folder = self.idsepchar.join( 126 [str(kwargs[k])for k in cls.identifiers])113 folder = self.idsepchar.join([str(kwargs[k]) 114 for k in cls.identifiers]) 127 115 if not folder: 128 116 folder = "__blank__" … … 136 124 return None 137 125 138 try: 139 expr = logic.combine(expr, kwargs) 140 return self.xrecall(cls, expr).next() 141 except StopIteration: 142 return None 126 return storage.StorageManager.unit(self, cls, **kwargs) 143 127 144 128 def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): trunk/dejavu/storage/storejson.py
r542 r543 91 91 return self.decoder.decode(v) 92 92 93 def unit(self, classes, expr=None, **kwargs): 94 """A single Unit/Unit set which matches the given expr, else None. 95 96 **kwargs will be combined into an Expression via logic.filter. 97 98 The first Unit (or set of Units, if 'classes' is a UnitJoin) 99 matching the expression is returned; if no Units match, None 100 is returned. 101 """ 102 if isinstance(classes, dejavu.UnitJoin): 103 try: 104 return self._xmultirecall(classes, expr, order=order, 105 limit=limit, offset=offset).next() 106 except StopIteration: 107 return None 108 109 cls = classes 110 if (expr is None) and set(kwargs.keys()) == set(cls.identifiers): 93 def unit(self, cls, **kwargs): 94 """A single Unit which matches the given kwargs, else None. 95 96 The first Unit matching the kwargs is returned; if no Units match, 97 None is returned. 98 """ 99 if set(kwargs.keys()) == set(cls.identifiers): 111 100 # Looking up a Unit by its identifiers. 112 101 # Skip walking the shelf. 113 102 if self.logflags & logflags.RECALL: 114 expr = logic.combine(expr, kwargs) 115 self.log(logflags.RECALL.message(cls, expr)) 103 self.log(logflags.RECALL.message(cls, kwargs)) 116 104 117 105 classdir = self.shelf(cls) 118 fname = self.idsepchar.join( 119 [str(kwargs[k])for k in cls.identifiers])106 fname = self.idsepchar.join([str(kwargs[k]) 107 for k in cls.identifiers]) 120 108 if not fname: 121 109 fname = "__blank__" … … 130 118 return unit 131 119 132 try: 133 expr = logic.combine(expr, kwargs) 134 return self.xrecall(cls, expr).next() 135 except StopIteration: 136 return None 120 return storage.StorageManager.unit(self, cls, **kwargs) 137 121 138 122 def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): trunk/dejavu/storage/storememcached.py
r542 r543 75 75 md5.new(pickle.dumps(ident)).hexdigest()) 76 76 77 def unit(self, classes, expr=None, **kwargs): 78 """A single Unit/Unit set which matches the given expr, else None. 79 80 **kwargs will be combined into an Expression via logic.filter. 81 82 The first Unit (or set of Units, if 'classes' is a UnitJoin) 83 matching the expression is returned; if no Units match, None 84 is returned. 85 """ 86 if isinstance(classes, dejavu.UnitJoin): 87 try: 88 return self._xmultirecall(classes, expr, order=order, 89 limit=limit, offset=offset).next() 90 except StopIteration: 91 return None 92 93 cls = classes 94 fullexpr = logic.combine(expr, kwargs) 77 def unit(self, cls, **kwargs): 78 """A single Unit which matches the given kwargs, else None. 79 80 The first Unit matching the kwargs is returned; if no Units match, 81 None is returned. 82 """ 95 83 if self.logflags & logflags.RECALL: 96 self.log(logflags.RECALL.message(cls, fullexpr))97 98 if (expr is None) andset(kwargs.keys()) == set(cls.identifiers):84 self.log(logflags.RECALL.message(cls, kwargs)) 85 86 if set(kwargs.keys()) == set(cls.identifiers): 99 87 # Looking up a Unit by its identifiers. 100 88 # Skip grabbing the cached class index (a HUGE optimization). 101 89 key = tuple([kwargs[k] for k in cls.identifiers]) 102 90 key = "%s:%s:%s" % (self.name, cls.__name__, 103 md5.new(pickle.dumps(key)).hexdigest())91 md5.new(pickle.dumps(key)).hexdigest()) 104 92 unit = self.client.get(key) 105 93 if unit is not None: … … 110 98 keys = self._get_class_index(cls) or set() 111 99 try: 112 return self._xrecall_inner(keys, fullexpr).next()[0] 100 expr = logic.filter(**kwargs) 101 return self._xrecall_inner(keys, expr).next()[0] 113 102 except StopIteration: 114 103 return None trunk/dejavu/storage/storeram.py
r542 r543 23 23 return lock 24 24 25 def unit(self, classes, expr=None, **kwargs): 26 """A single Unit/Unit set which matches the given expr, else None. 27 28 **kwargs will be combined into an Expression via logic.filter. 29 30 The first Unit (or set of Units, if 'classes' is a UnitJoin) 31 matching the expression is returned; if no Units match, None 32 is returned. 33 """ 34 if isinstance(classes, dejavu.UnitJoin): 35 try: 36 return self._xmultirecall(classes, expr, order=order, 37 limit=limit, offset=offset).next() 38 except StopIteration: 39 return None 40 41 cls = classes 42 fullexpr = logic.combine(expr, kwargs) 25 def unit(self, cls, **kwargs): 26 """A single Unit which matches the given kwargs, else None. 27 28 The first Unit matching the kwargs is returned; if no Units match, 29 None is returned. 30 """ 43 31 if self.logflags & logflags.RECALL: 44 self.log(logflags.RECALL.message(cls, fullexpr))32 self.log(logflags.RECALL.message(cls, kwargs)) 45 33 46 34 lock = self._get_lock(cls) … … 48 36 cache = self._caches[cls] 49 37 50 if (expr is None) andset(kwargs.keys()) == set(cls.identifiers):38 if set(kwargs.keys()) == set(cls.identifiers): 51 39 # Looking up a Unit by its identifiers. 52 40 # Skip grabbing the cached class index (a HUGE optimization). … … 61 49 62 50 try: 63 return self._xrecall_inner(cache, cache.keys(), fullexpr 51 expr = logic.filter(**kwargs) 52 return self._xrecall_inner(cache, cache.keys(), expr 64 53 ).next()[0] 65 54 except StopIteration: trunk/dejavu/storage/storeshelve.py
r542 r543 49 49 self.locks = {} 50 50 51 def unit(self, classes, expr=None, **kwargs): 52 """A single Unit/Unit set which matches the given expr, else None. 53 54 **kwargs will be combined into an Expression via logic.filter. 55 56 The first Unit (or set of Units, if 'classes' is a UnitJoin) 57 matching the expression is returned; if no Units match, None 58 is returned. 59 """ 60 if isinstance(classes, dejavu.UnitJoin): 61 try: 62 return self._xmultirecall(classes, expr, order=order, 63 limit=limit, offset=offset).next() 64 except StopIteration: 65 return None 66 67 cls = classes 68 fullexpr = logic.combine(expr, kwargs) 51 def unit(self, cls, **kwargs): 52 """A single Unit which matches the given kwargs, else None. 53 54 The first Unit matching the kwargs is returned; if no Units match, 55 None is returned. 56 """ 69 57 if self.logflags & logflags.RECALL: 70 self.log(logflags.RECALL.message(cls, fullexpr))58 self.log(logflags.RECALL.message(cls, kwargs)) 71 59 72 60 data = self.shelves[cls] … … 74 62 return None 75 63 76 if (expr is None) andset(kwargs.keys()) == set(cls.identifiers):64 if set(kwargs.keys()) == set(cls.identifiers): 77 65 # Looking up a Unit by its identifiers. 78 66 # Skip grabbing the entire shelf (a HUGE optimization). … … 91 79 92 80 try: 93 return self._xrecall_inner(cls, data, fullexpr).next()[0] 81 expr = logic.filter(**kwargs) 82 return self._xrecall_inner(cls, data, expr).next()[0] 94 83 except StopIteration: 95 84 return None trunk/dejavu/test/test_dejavu.py
r542 r543 136 136 self.assertEqual(bat3.Legs, 4) 137 137 138 def test_Subclassing(self):139 box = store.new_sandbox()140 box.memorize(Visit(VetID=1, ZooID=1, AnimalID=1))141 box.memorize(Visit(VetID=1, ZooID=1, AnimalID=2))142 box.memorize(Visit(VetID=2, ZooID=1, AnimalID=3))143 box.memorize(Lecture(VetID=1, ZooID=1, Topic='Cage Cleaning'))144 box.memorize(Lecture(VetID=1, ZooID=1, Topic='Ape Mating Habits'))145 box.memorize(Lecture(VetID=2, ZooID=3, Topic='Your Tiger and Steroids'))146 147 visits = box.recall(Visit, inherit=True)148 self.assertEqual(len(visits), 6)149 150 box.flush_all()151 152 box = store.new_sandbox()153 visits = box.recall(Visit, inherit=True)154 self.assertEqual(len(visits), 6)155 cc = [x for x in visits156 if getattr(x, "Topic", None) == "Cage Cleaning"]157 self.assertEqual(len(cc), 1)158 159 self.assertEqual(len(box.recall(Visit, inherit=True, AnimalID=2)), 1)160 self.assertEqual(len(box.recall(Lecture, inherit=True, AnimalID=2)), 0)161 162 138 def test_UnitJoin(self): 163 139 box = store.new_sandbox() … … 194 170 def test_json(self): 195 171 try: 196 from dejavu.json import Converter, Encoder, Decoder172 from dejavu.json import * 197 173 except ImportError: 198 174 print "JSON funtionality requires the simplejson package." 199 175 return 200 176 201 json = Converter( store)177 json = Converter() 202 178 box = store.new_sandbox() 203 179 … … 215 191 216 192 zoo = box.unit(Zoo, Name="San Diego Zoo") 217 zoo_json = json.dumps( zoo)193 zoo_json = json.dumps(unit_to_dict(zoo)) 218 194 219 195 self.assert_("1916-10-02" in zoo_json) 220 self.assert_("__dejavu. Unit__" in zoo_json)196 self.assert_("__dejavu.class__" in zoo_json) 221 197 222 198 zoo_json = zoo_json.replace("San Diego Zoo", "The San Diego Zoo") 223 zoo2 = json.loads(zoo_json)199 zoo2 = dict_to_unit(json.loads(zoo_json)) 224 200 225 201 self.assertEqual(zoo2.Name, "The San Diego Zoo") trunk/dejavu/test/zoo_fixture.py
r542 r543 466 466 # We flush_all to ensure a DB hit each time. 467 467 box.flush_all() 468 return len(box.recall(cls, (lam)))468 return len(box.recall(cls, lam)) 469 469 470 470 zoos = box.recall(Zoo) … … 969 969 try: 970 970 # Test box.unit inside of xrecall 971 for visit in box.xrecall(Visit, VetID=1):971 for visit in box.xrecall(Visit, dict(VetID=1)): 972 972 firstvisit = box.unit(Visit, VetID=1, Date=Jan_1_2001) 973 973 self.assertEqual(firstvisit.VetID, 1) … … 975 975 976 976 # Test recall inside of xrecall 977 for visit in box.xrecall(Visit, VetID=1):977 for visit in box.xrecall(Visit, dict(VetID=1)): 978 978 f = (lambda x: x.VetID == 1 and x.ID != visit.ID) 979 979 othervisits = box.recall(Visit, f) … … 981 981 982 982 # Test far associations inside of xrecall 983 for visit in box.xrecall(Visit, VetID=1):983 for visit in box.xrecall(Visit, dict(VetID=1)): 984 984 # visit.Vet is a ToOne association, so will return a unit or None. 985 985 vet = visit.Vet() … … 991 991 box = root.new_sandbox() 992 992 try: 993 quadrupeds = box.recall(Animal, Legs=4)993 quadrupeds = box.recall(Animal, dict(Legs=4)) 994 994 self.assertEqual(len(quadrupeds), 4) 995 995 … … 1039 1039 eng.rules()[-1].forget() 1040 1040 self.assertEqual(eng.FinalClassName, "Animal") 1041 finally:1042 box.flush_all()1043 1044 def test_Subclassing(self):1045 box = root.new_sandbox()1046 try:1047 box.memorize(Visit(VetID=21, ZooID=1, AnimalID=1))1048 box.memorize(Visit(VetID=21, ZooID=1, AnimalID=2))1049 box.memorize(Visit(VetID=32, ZooID=1, AnimalID=3))1050 box.memorize(Lecture(VetID=21, ZooID=1, Topic='Cage Cleaning'))1051 box.memorize(Lecture(VetID=21, ZooID=1, Topic='Ape Mating Habits'))1052 box.memorize(Lecture(VetID=32, ZooID=3, Topic='Your Tiger and Steroids'))1053 1054 visits = box.recall(Visit, inherit=True, ZooID=1)1055 self.assertEqual(len(visits), 5)1056 1057 box.flush_all()1058 1059 box = root.new_sandbox()1060 visits = box.recall(Visit, inherit=True, VetID=21)1061 self.assertEqual(len(visits), 4)1062 cc = [x for x in visits1063 if getattr(x, "Topic", None) == "Cage Cleaning"]1064 self.assertEqual(len(cc), 1)1065 1066 # Checking for non-existent attributes in/from subclasses1067 # isn't supported yet.1068 ## f = logic.filter(AnimalID=2)1069 ## self.assertEqual(len(box.recall(Visit, f)), 1)1070 ## self.assertEqual(len(box.recall(Lecture, f)), 0)1071 1041 finally: 1072 1042 box.flush_all() … … 1550 1520 global root 1551 1521 store.register_all(globals()) 1522 engines.register_classes(store) 1523 dejavu.DeployedVersion.register(store) 1552 1524 if hasattr(store, "cache"): 1553 1525 store.cache.register_all(globals()) 1526 engines.register_classes(store.cache) 1527 dejavu.DeployedVersion.register(store.cache) 1554 1528 if hasattr(store, "nextstore"): 1555 1529 store.nextstore.register_all(globals()) 1556 engines.register_classes(store)1557 dejavu.DeployedVersion.register(store)1530 engines.register_classes(store.nextstore) 1531 dejavu.DeployedVersion.register(store.nextstore) 1558 1532 1559 1533 if mediated: trunk/dejavu/units.py
r542 r543 13 13 import warnings 14 14 15 from dejavu import errors 15 from dejavu import errors, logic 16 16 17 17 … … 78 78 raise AttributeError("Unit Associations may not be deleted.") 79 79 80 def related(self, unit, expr=None, **kwargs):80 def related(self, unit, expr=None, order=None, limit=None, offset=None): 81 81 """Return unit(s) on the far side of this relation.""" 82 82 raise NotImplementedError … … 88 88 to_many = False 89 89 90 def related(self, unit, expr=None, **kwargs):90 def related(self, unit, expr=None, order=None, limit=None, offset=None): 91 91 """Return the single unit on the far side of this relation.""" 92 92 value = getattr(unit, self.nearKey) … … 94 94 return None 95 95 96 kwargs.setdefault(self.farKey, value) 97 units = unit.sandbox.xrecall(self.farClass, expr, **kwargs) 96 expr = logic.combine(expr, {self.farKey: value}) 97 units = unit.sandbox.xrecall(self.farClass, expr, order=order, 98 limit=1, offset=offset) 98 99 try: 99 100 return units.next() … … 107 108 to_many = True 108 109 109 def related(self, unit, expr=None, **kwargs):110 def related(self, unit, expr=None, order=None, limit=None, offset=None): 110 111 """Return all units on the far side of this relation.""" 111 112 value = getattr(unit, self.nearKey) … … 113 114 return [] 114 115 115 kwargs.setdefault(self.farKey, value) 116 return unit.sandbox.recall(self.farClass, expr, **kwargs) 116 expr = logic.combine(expr, {self.farKey: value}) 117 return unit.sandbox.recall(self.farClass, expr, order=order, 118 limit=limit, offset=offset) 117 119 118 120
