Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

Changeset 543

Show
Ignore:
Timestamp:
10/06/07 22:38:27
Author:
fumanchu
Message:

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

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/dejavu/__init__.py

    r542 r543  
    3838import datetime as _datetime 
    3939 
    40 from geniusql import logic 
    41 from geniusql import logicfuncs 
     40from geniusql import logic, logicfuncs, _AttributeDocstrings 
    4241 
    4342from dejavu import logflags 
     
    5150 
    5251class Query(object): 
    53     """A query with relation, attributes, and restriction expressions. 
     52    """A query with relation, attributes, and restriction expressions.""" 
    5453     
    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 
    7555     
    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): 
    7784        self.relation = relation 
    7885         
     
    8491        if restriction is None: 
    8592            restriction = logic.Expression(lambda *args: True) 
     93        elif isinstance(restriction, dict): 
     94            restriction = logic.filter(**restriction) 
    8695        elif not isinstance(restriction, logic.Expression): 
    8796            restriction = logic.Expression(restriction) 
    88         restriction = logic.combine(restriction, kwargs) 
    8997         
    9098        self.restriction = restriction 
  • trunk/dejavu/doc/index.html

    r542 r543  
    3232        <li>Unit Properties are First-Class Objects</li> 
    3333        <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> 
    3736        </ul> 
    3837    </li> 
     
    4443        </ul> 
    4544    </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> 
    5145    <li><a href='modeling.html#schemas'>Managing Schemas</a> 
    5246        <ul> 
     47        <li>Conflicts</li> 
    5348        <li>Installation</li> 
    5449        <li>Modifying Storage Structures</li> 
     
    10297<li><a href='storage.html'>Deployers: Configuring Storage</a> 
    10398    <ul> 
    104     <li><a href='storage.html#configuration'>Common Configuration Entries</a></li> 
    10599    <li><a href='storage.html#databases'>Database Storage Managers</a> 
    106100        <ul> 
     
    116110        <ul> 
    117111        <li>RAM</li> 
     112        <li>Memcached</li> 
     113        <li>JSON</li> 
    118114        <li>Shelve</li> 
    119115        <li>Folders</li> 
     
    122118    <li><a href='storage.html#middleware'>Middleware</a> 
    123119        <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> 
    126128        </ul> 
    127129    </li> 
     
    132134    <ul> 
    133135    <li>Subclassing Sandbox</li> 
    134     <li>Stor Hacking</li> 
     136    <li>Store Hacking</li> 
    135137    <li>Passing through SQL</li> 
    136138    <li>Custom Storage Managers 
  • trunk/dejavu/doc/managing.html

    r542 r543  
    2020Storage Manager.</p> 
    2121 
    22 <p>You can create Sandbox objects directly. They take a single argument, the 
    23 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 Arena 
    30 object, and would rather avoid importing dejavu yet again just to obtain 
     22<p>You can create Sandbox objects directly. They take a single argument, a 
     23<tt>StorageManager</tt> object. All Storage Managers also provide a 
     24convenience function, <tt class='def'>new_sandbox</tt>, which does this 
     25for you. The following lines are equivalent: 
     26<pre>box = dejavu.Sandbox(store
     27 
     28box = store.new_sandbox()</pre> 
     29You might often choose the latter when you have a reference to the store, 
     30and would rather avoid importing dejavu yet again just to obtain 
    3131the Sandbox class.</p> 
    3232 
     
    4444 
    4545<p>Memorization does several things. First, it places your new Unit into 
    46 your Arena. That Unit instance will now be persisted by the appropriate 
     46your store. That Unit instance will now be persisted by the appropriate 
    4747Storage Manager. It can be recalled from storage when needed, using the 
    4848built-in Expression syntax. It may have been given an ID (see 
     
    6363you like. As long as you provide your own identifier values for Units, 
    6464nothing 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 Sandbox 
     65However, if you memorize a Unit with an ID of <tt>None</tt>, the store 
    6666may attempt to provide an ID for it.</p> 
    6767 
     
    8686accomplish this.</p> 
    8787 
    88 <h5>recall()</h5
     88<a name='recall'><h5>recall()</h5></a
    8989<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 instanc
    94 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 be 
    97 combined into an Expression for you
     90<tt class='def'>recall(cls, expr=None, order=None, limit=None, offset=None)</tt> 
     91function. This is the full-blown query method. As a first argument, you 
     92pass it the class (<b>not</b> the name of the class, but the actual class) 
     93of which you expect to retrieve instances. The second argument should b
     94a lambda or an instance of <tt>dejavu.logic.Expression</tt> (an object which 
     95encapsulates your specific query, see <a href='#Querying'>Querying</a>). 
     96Alternately, you may supply a dict, which will then be combined into an 
     97Expression for you (using <a href='#filter'><tt>logic.filter</tt></a>)
    9898The 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)
    100100>>> units = box.recall(Book, lambda x: x.Year == 1928) 
    101101>>> units = box.recall(Book, logic.Expression(lambda x: x.Year == 1928)) 
     
    104104 u'Tarzan, The Lord of the Jungle'] 
    105105</pre> 
    106 If you do not supply an Expression or any keyword args, all Units of the 
     106If you do not supply an expression, all Units of the 
    107107given Unit class will be retrieved in a list.</p> 
    108108 
     
    110110called when each Unit has been loaded from storage (at the end of the 
    111111recall 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/SM 
    113 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 Sandbox 
     112<tt>on_recall</tt> will not be called again; it's only called at the 
     113Sandbox/SM boundary. If <tt>on_recall</tt> raises <tt>UnrecallableError</tt>, 
     114the unit will not be yielded back to the caller, nor placed in the Sandbox 
    115115cache.</p> 
    116116 
    117 <h5>Recalling multiple classes at once (JOINs)</h5
     117<a name='joins'><h5>Recalling multiple classes at once (JOINs)</h5></a
    118118 
    119119<p>In addition to providing a single class to <tt>recall</tt>, you have 
     
    122122instances.</p> 
    123123 
    124  
    125124<p>The "leftbiased" argument specifies how the results will be joined:</p> 
    126125 
    127126<table> 
    128 <tr><th>leftbiased</th><th>Join&nbsp;Type</th><th>Description</th><th>Operator</th></tr> 
     127<tr> 
     128    <th>leftbiased</th> 
     129    <th>Join&nbsp;Type</th> 
     130    <th>Description</th> 
     131    <th>Operator</th> 
     132</tr> 
    129133<tr> 
    130134    <td>None</td> 
     
    165169<p>When you provide multiple classes, the <tt>recall</tt> method returns 
    166170a 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 
     173given row at once (you cannot use a dict expr with multiple classes)
    170174 
    171175<pre>for pub, book in box.recall(Publisher & Book, lambda p, b: p.ID == 4)</pre> 
     
    184188 
    185189<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> 
    187191 
    188192<h5>xrecall()</h5> 
     
    193197<p>The <tt>recall</tt> method can be verbose. When you want a one-liner 
    194198and only expect a single Unit, use the 
    195 <tt class='def'>unit(cls, expr=None, inherit=False, **kw)</tt> method 
     199<tt class='def'>unit(cls, **kw)</tt> method 
    196200of 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 
     201as the first argument. Then, supply keyword arguments of the form 
    199202"property_name=value". The method will form an equivalent Expression 
    200203for you from the keyword args. For example: 
     
    205208If no Unit can be found that matches the criteria, None is returned. 
    206209If 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 
     213all stores) for retrieving a single Unit by its identifiers. When using 
     214key-value stores like <a href='http://www.danga.com/memcached/'>memcached</a> 
     215in your storage manager network, calling <tt>unit</tt> may be much faster 
     216than <tt>recall</tt>, even for multiple units.</p> 
    208217 
    209218<h5>"Magic recaller" methods</h5> 
    210 <p>For each class you have registered with your Arena, the Sandbox will 
     219<p>For each class you have registered with your store, the Sandbox will 
    211220have a "magic recaller" method of the same name, to make single-unit 
    212221lookups easier. Instead of the above example for <tt>box.unit()</tt>, 
     
    263272units in the sandbox. For example, if you do this:</p> 
    264273 
    265 <pre>box = arena.new_sandbox() 
     274<pre>box = store.new_sandbox() 
    266275thing = box.unit(Thing) 
    267276box.flush_all() 
     
    279288<h4>Views</h4> 
    280289<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. 
    282291This works like <tt>recall</tt>, but returns values, rather than Units. 
     292The 'query' argument should be an instance of <tt>dejavu.Query</tt>, 
     293or more commonly, a 3-tuple of (cls, attrs, expr). 
    283294Put simply, it yields all values 
    284295for the given attribute(s) of the Unit class provided; each unit will 
    285296yield 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']) 
     297sequence you provide. Providing an expr argument (a lambda, an 
     298<tt>Expression</tt> object, or a dict, see below), will filter the 
     299set of Units before obtaining the value tuples.</p> 
     300 
     301<pre>>>> v = sandbox.view((zoo.Animal, ['Name', 'Lifespan'])) 
    292302>>> [row for row in v]    # or list(v), or iterate over v... 
    293303[('Leopard', 73.5), 
     
    308318be an iterable.</p> 
    309319 
    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 
    313321tuples rather than all tuples.</p> 
    314322 
    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> 
     324function by passing <tt>attrs = cls.identifiers</tt> and setting 
     325'distinct' to True. 
     326Sandboxes provide a <tt class='def'>count(cls, expr=None)</tt> 
    318327method which does just this.</p> 
    319328 
    320 <p>Dejavu 1.5 adds two new sandbox methods: range and sum. The 
    321 <tt class='def'>range(cls, attr, expr, **kw)</tt> method takes a single 
     329<p>There are two additional sandbox methods for aggregates: range and sum. 
     330The <tt class='def'>range(cls, attr, expr=None)</tt> method takes a single 
    322331attribute and returns the closed interval [min(attr), ..., max(attr)]. The 
    323 <tt class='def'>sum(cls, attr, expr, **kw)</tt> method also takes a single 
     332<tt class='def'>sum(cls, attr, expr=None)</tt> method also takes a single 
    324333attribute and returns the sum of all non-None values for the given 
    325334cls.attr.</p> 
    326335 
     336<h5>xview()</h5> 
     337<p>Just like view, but returns an iterator instead of a list. Use xview 
     338to load Unit values in a more lazy fashion.</p> 
     339 
     340 
    327341 
    328342<h4>Transactions</h4> 
    329343<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). 
     344does not yet use distributed two-phase commit! That's planned for later). 
    331345Most often, your code will call transaction methods on the current  
    332346sandbox object. 
     
    488502 
    489503<p>Second, any globals or cell references must <b>also</b> be present in 
    490 the <tt>logic</tt> module's globals when the Expression is unpickled. 
     504the <tt>logic</tt> module when the Expression is unpickled. 
    491505Pickling occurs when Expressions are sent over sockets, and also if 
    492506Expressions are themselves persisted to storage (for example, see 
    493 <u>Unit Engines</u>, below). This means your application should inject 
    494 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 
     508such global references in <tt>logic.builtins</tt> (a dict). Note that 
     509the <tt>logic</tt> module already tries to import <tt>datetime</tt>, 
     510<tt>fixedpoint</tt> and <tt>decimal</tt>.</p> 
    497511 
    498512<h4>External functions within Expressions</h4> 
    499513<p>Dejavu provides additional functions which can be used in Expressions. 
    500514For 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> 
    502516In this example, the <tt>today()</tt> function breaks convention and is 
    503517actually <b>bound late</b>. That is, if you construct this Expression now 
     
    505519Storage Managers "know about" these dejavu functions, and can use them 
    506520to build more appropriate queries. Here are the functions supplied by 
    507 the <tt>dejavu</tt> module:</p> 
     521Dejavu:</p> 
    508522 
    509523<table> 
     
    553567    <td>Y</td> 
    554568    <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> 
    555574</tr> 
    556575<tr> 
     
    581600say, SQL), and thereby achieve end-to-end functionality.</p> 
    582601 
    583 <h4>Using <tt>filter</tt> to form Expressions</h4
     602<a name='filter'><h4>Using <tt>filter</tt> to form Expressions</h4></a
    584603<p>The <tt>logic</tt> module also provides convenient methods to 
    585604create common types of Expression objects via the <tt>filter</tt> and 
     
    622641Although the comparison function only allows a single comparison at a time, 
    623642the resulting Expressions can be combined with the <tt>&</tt> and <tt>|</tt> 
    624 operators (described earlier) to produce more complex Expressions.</p> 
     643operators (see next) to produce more complex Expressions.</p> 
    625644 
    626645<h4>Combining Expressions</h4> 
     
    671690<p>In particular, <tt>logic.Expression</tt> objects can operate on <i>any</i> 
    672691Python objects, not just dejavu <tt>Unit</tt> instances. If you wish to 
    673 provide additional logic functions (as dejavu does), simply inject them 
    674 into <tt>logic</tt>'s globals.</p> 
     692provide additional logic functions (as dejavu does), simply add them 
     693to <tt>logic.builtins</tt>.</p> 
    675694 
    676695<p>You may also find the underlying <tt>codewalk</tt> module useful for 
     
    785804<tr> 
    786805    <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> 
    788807        dict</td> 
    789808    <td>Calls the function, passing the current Set. The function 
     
    795814    <td>Transform the current Set into a Set of associated Units 
    796815        (of another Type). The association must be present in the 
    797         <tt>Arena.associations</tt> graph.</td> 
     816        <tt>store.associations</tt> graph.</td> 
    798817</tr> 
    799818<tr> 
     
    864883<h4>Engine Functions</h4> 
    865884<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. 
     885rule is a string, a key in the <tt>store.engine_functions</tt> dictionary. 
    867886When the rule is executed, that key is used to look up the function, which 
    868887is then called, passing <tt>(sandbox, set)</tt>. The function should 
     
    877896 
    878897<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 
     900in your Python code. Dejavu provides a <tt class='def'>sort(attrs)</tt> 
     901function to assist you in sorting Units. It returns a function, which you 
     902can then use in Python's sort function (which operates in place). 
     903Continuing our example: 
     904<pre>people.sort(dejavu.sort(['Size DESC', 'Name']))</pre> 
    886905The most important issue (and the reason we don't just use 2.4's attrgetter), 
    887906is that any Unit property must allow values of None, which tends to raise 
  • trunk/dejavu/doc/modeling.html

    r542 r543  
    8686</pre> 
    8787 
    88 <p>Every Unit must possess at least one identifier. This ensures that 
     88<p>Every Unit should possess at least one identifier. This ensures that 
    8989each Unit within the system is unique. You should consider any 
    9090UnitProperty which is one of the identifiers to be read-only 
    91 after a Unit has been memorized.</p> 
     91after a Unit has been memorized. Extremely rare applications 
     92(like write-only log tables) are allowed to use en empty identifiers 
     93tuple, but in most OLTP/OLAP scenarios, all your Units should 
     94have at least one identifier property.</p> 
    9295 
    9396<h4>Creating and Populating Properties</h4> 
     
    245248already been set on the unit.</p> 
    246249 
    247 <h4>Registration of Unit Classes</h4
     250<a name='registering'><h4>Registration of Unit Classes</h4></a
    248251<p>In addition to defining your Unit class, you must also register that 
    249252class with your application's <tt>StorageManager</tt>. Each class which 
     
    259262you have defined between Units.</p> 
    260263 
    261  
    262 <h4>Synchronizing the Model</h4> 
     264<p>If you're using multiple StorageManagers in a network, you must 
     265register classes for each of them. You can inspect which classes have 
     266been registered to a given store via <tt class='def'>store.classes</tt>, 
     267a 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 
     271between Unit classes in its <tt class='def'>associations</tt> attribute, 
     272which is a simple, unweighted, undirected graph. Whenever you register 
     273a Unit class, the SM will add its associations to this graph. The only 
     274other common operation is to call 
     275<tt class='def'>associations.shortest_path(start, end)</tt>, 
     276to retrieve the chain of associations between two Unit classes.</p> 
     277 
     278 
     279<a name='synchronizing'><h4>Synchronizing the Model</h4></a> 
    263280 
    264281<p>Any database code in a general-purpose programming language will 
     
    272289to know the database types in effect in order to translate data safely.</p> 
    273290 
    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> 
     293you have registered all of your Unit classes (but before you attempt 
     294to execute commands on them).</p> 
    282295 
    283296<p>If your application has created all of its own tables using Dejavu, 
     
    292305 
    293306 
    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): pass 
    300 <br />class Cash(Payment): pass 
    301 <br />class Credit(Payment): pass 
    302 </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 />[&lt;bill.Payment object at 0x01158E10>, 
    309 <br /> &lt;bill.Cash object at 0x0118B350>, 
    310 <br /> &lt;bill.Credit object at 0x0118B170>] 
    311 </code></p> 
    312  
    313 <p>This also works for the <tt>xrecall</tt> and <tt>unit</tt> methods, but 
    314 only when providing a single class (none of them retrieve subclasses when 
    315 recalling joined classes yet). Currently, you cannot reference a property 
    316 (in an Expression) which is not present in both the superclass and all of 
    317 the subclasses.</p> 
    318  
    319 <p><b>The 'inherit' argument is new in 1.5, and defaults to False.</b></p> 
    320  
    321307<a name='associations'><h3>Associations between Unit Classes</h3></a> 
    322308<p>Once you've put together some Unit classes, chances are you're going to 
     
    346332relationship objects.</p> 
    347333 
    348 <p>What does an explicit association buy for you? First, you can associate 
    349 Units without having to remember which keys are related. Second, 
    350 StorageManagers 
     334<p>What does an explicit association buy for you? First, you can 
     335<a href='managing.html#joins'>join</a> Units without having to remember 
     336which keys are related. Second, StorageManagers 
    351337discover associations and fill the <tt>store.associations</tt> registry, so 
    352338that smart consumer code (like <a href='managing.html#unitenginerules'>Unit 
     
    397383<pre>>>> Archaeologist.Biography 
    398384&lt;unbound method Archaeologist.related_units> 
     385 
    399386>>> Eversley = Archaeologist(Height=6.417) 
    400387>>> Eversley.Biography 
    401388&lt;bound method Archaeologist.related_units of &lt;__main__.Archaeologist 
    402389object at 0x011A1930>> 
     390 
    403391>>> bios = Eversley.Biography() 
    404392>>> bios 
     
    412400easily. At the other extreme (when you have hundreds of Biographies to filter), 
    413401you 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>. 
     402to the "related units" method, just like you can with 
     403<a href='managing.html#recall'><tt>recall</tt></a>. 
    415404When you do, the list of associated Units will be filtered accordingly.</p> 
    416405 
     
    422411(or None if there is no matching Unit). When retrieving "to-many", 
    423412the 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> 
     413no matches).</p> 
    426414 
    427415<p>Because the "related units" method names are formed automatically, you need 
     
    457445     
    458446    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 None 
     447        bios = unit.Biography(expr, order=["PubDate DESC"], **kwargs) 
     448        try
     449            return bios.next(
     450        except StopIteration: 
     451            return None 
    464452 
    465453descriptor = LastBiographyAssociation(u'ID', Biography, u'ID') 
     
    475463Unit, or None. Finally, the <tt>register</tt> attribute, when False, 
    476464keeps 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> 
    502466 
    503467 
    504468<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 
     474model and reality can change independently, you'll find <i>conflicts</i> 
     475between them from time to time. The most common occurrence of such conflicts 
     476is during a call to <tt>map_all</tt>, since it tries to match up your entire 
     477model to reality. Similar conflicts arise whenever you ask Dejavu to make 
     478changes 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 
     481the method arguments. The value you supply for this argument tells Dejavu 
     482what 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> 
    505497 
    506498<h4>Installation</h4> 
     
    509501Dejavu doesn't try to over-engineer it. But the deployer will still have 
    510502to go through an installation step at some point. Dejavu offers minimal 
    511 library calls which you can then build installation (and upgrade, and 
    512 uninstall) tools on top of.</p> 
     503library calls on top of which you can then build installation tools 
     504(and upgrade, and uninstall tools).</p> 
    513505 
    514506<p>For example, a simple install process could look like this:</p> 
     
    519511    store.logflags = logflags.ERROR + logflags.SQL + logflags.SANDBOX 
    520512     
    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     
    532521    sys.exit(0) 
    533522</pre> 
    534523 
    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>, 
     525all Storage Managers also have a 
     526<tt class='def'>drop_database(conflicts='error')</tt> method.</p> 
    543527 
    544528<h4>Modifying Storage Structures</h4> 
     
    560544<p>Assuming we've already made the change to our model, the above example 
    561545renames 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> 
     547method. Additional <tt>StorageManager</tt> methods:</p> 
    564548 
    565549<p>Unit classes (tables):</p> 
    566550<ul> 
    567 <li><tt class='def'>create_storage(cls)</tt></li> 
     551<li><tt class='def'>create_storage(cls, conflicts='error')</tt></li> 
    568552<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> 
    570554</ul> 
    571555 
    572556<p>Unit properties (columns):</p> 
    573557<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> 
    575559<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> 
    577561</ul> 
    578562 
    579563<p>Unit property (column) indices:</p> 
    580564<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> 
    582566<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> 
    584568</ul> 
    585569 
     
    654638 
    655639<p>When you create your first Dejavu model, you might be forming it 
    656 to match some existing database schema. If so, Dejavu 1.5 has a 
    657 new <tt>Modeler</tt> tool to help you inside <tt>dejavu.storage.db</tt>. 
     640to match some existing database schema. If so, Dejavu has a 
     641<tt>Modeler</tt> tool to help you inside <tt>dejavu.storage.db</tt>. 
    658642</p> 
    659643 
  • trunk/dejavu/doc/storage.html

    r542 r543  
    2424 
    2525<h2>Deployers: Configuring Storage</h2> 
     26 
     27<p>The topmost object in Dejavu is a <tt>StorageManager</tt> object. 
     28When building a Dejavu application, you must first create a 
     29StorageManager instance, and must find a way to persist this object 
     30across client connections. This can be achieved in multiple ways; 
     31web applications, for example, will typically create a single process 
     32to serve all requests. Desktop applications will probably create a 
     33single StorageManager object for each running instance of the program.</p> 
    2634 
    2735<p>Storage Managers insulate an application developer from the specifics of 
     
    3240application developer will have already prepared default config files 
    3341which 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> 
     42over your data storage, you have it.</p> 
    3843 
    3944<p>When you deploy an app built with Dejavu, you must specify Storage 
    4045Managers 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;" 
     46done through an ini-style configuration file, although Dejavu itself 
     47doesn't currently provide a parser for that. Here's a short example 
     48of configuring a store in Python: 
     49<pre> 
     50from dejavu import storage 
     51opts = {'connections.Connect': "PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=D:\data\junct.mdb;"} 
     52root = storage.resolve("access", opts) 
    4553</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. 
     54The <tt class='def'>storage.resolve(store, options=None)</tt> call 
     55tells Dejavu the <i>class</i> of SM we'd like to use. 
    5056For most applications, you'll decide which class to use based on the 
    5157database 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> 
     58application data in an "MS Access" (i.e., Jet) database. You may supply a 
     59known short name (like "sqlite") or the full dotted-package name. 
     60<tt class='def'>storage.managers</tt> is a dict of short names to 
     61full classes (or dotted class names). If you're including Dejavu in 
     62a 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 
     65string. The MS Access class requires this entry; other SM's may not.</p> 
    9666 
    9767 
     
    10070<h4>Microsoft SQL Server / Microsoft Access (Jet)</h4> 
    10171<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 
    10984        <a href='http://support.microsoft.com/?kbid=193332'>Microsoft</a>.</li> 
    11085</ul> 
    11186 
    112 <h4>PostgreSQL (pyPgSQL)</h4> 
     87<h4>PostgreSQL</h4> 
    11388<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), 
    11591    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, 
    133106        <tt>"host=localhost dbname=myapp user=postgres password=hilar1ous"</tt>. 
    134107        See the <a href='http://www.postgresql.org/docs/current/static/libpq.html'>libpq</a> 
     
    140113    mysql  Ver 14.7 Distrib 4.1.8, for Win95/Win98 (i32), 
    141114    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> 
    146126    <li>Connection arguments: any of "host", "user", "passwd", "db", "port", 
    147127        "unix_socket", "client_flag".<br />See the 
     
    155135    sqlite 3.3.3 (pysqlite-1.1.7.win32-py2.4), 
    156136    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. 
    158139    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> 
    165151    <li><b>Mode:</b> Optional. DB file mode. Defaults to 0755.</li> 
    166152</ul> 
     
    168154 
    169155<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> 
    176172    <li><b>Name:</b> Filename of the database. Must be an absolute path.</li> 
    177173    <li><b>Host:</b> The TCP host name, usually "localhost".</li> 
     
    191187<tr><th>Key</th><th>Example Value</th><th>Description</th></tr> 
    192188<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> 
    201190    <td><tt>myapp_</tt></td> 
    202191    <td>Optional. If specified, all tables in the database will have names 
     
    206195</tr> 
    207196<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> 
    242220    <td><tt>"READ COMMITTED"</tt></td> 
    243221    <td>Optional. All database SM's already have a value for this, but you 
     
    254232<h4>RAM</h4> 
    255233<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. 
     244This is an extremely simple implementation; every value that is not 
     245of type <tt>str</tt> or <tt>int</tt> is pickled. Querying will be slow-- 
     246every Unit is sucked in one-by-one and tested in pure Python. 
     247But 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 
     274contains files, one per Unit, with the Unit identity as the file name. 
     275Each of those unit files contains a JSON dict of Unit property values. 
     276For example:</p> 
     277 
     278<pre> 
     279root/ 
     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 
     288pure Python. This is a good choice for test data or system tables--store 
     289the data in JSON format for pretty version-control diffs, then migrate it 
     290to 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 
    256317 
    257318<h4>Shelve</h4> 
     
    273334is called on program exit.</p> 
    274335 
    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> 
    279341    <li><b>Path:</b> The file path (directory) in which to place db files. 
    280342        Each Unit subclass will get its own file, of the same name as the 
     
    310372for example, an upload site may only need files looked up by ID.</p> 
    311373 
    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> 
    316379    <li><b>root:</b> Required. The file path (directory) in which to 
    317380        place db files. Each Unit class will get its own subfolder, 
     
    336399<p>Some Storage Managers act as "middleware", and can be chained together 
    337400to provide layered functionality. Consider, for example, the 
    338 <tt>CachingProxy</tt> class; it has another Storage Manager 
     401<tt>ObjectCache</tt> class; it has another Storage Manager 
    339402"behind it", which it proxies. It can be used to cache objects between 
    340403client connections independently from the underlying, database-specific 
    341404Storage Manager. The beauty of this design is that the decision to 
    342 use a CachingProxy is completely up to the deployer, <i>not</i> the 
     405use a ObjectCache is completely up to the deployer, <i>not</i> the 
    343406application developer. The deployer can separate stores, test response 
    344407times, and address other integration concerns on their own systems.</p> 
    345408 
    346 <h4>Caching Proxy</h4> 
     409<h4>Object Cache</h4> 
    347410<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> 
     411It 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. 
     425It 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> 
    353435    <li><b>Lifetime:</b> Optional. The recurrence string which declares 
    354436        how often to sweep Units out of the in-memory cache. The string you 
     
    370452 
    371453 
    372 <h4>Burned Proxy</h4> 
     454<h4>Burned Cache</h4> 
    373455<p>Use this class to persist Units in memory between client connections. 
    374 It needs another Storage Manager to proxy. Unlike the Caching Proxy above, 
     456It needs another Storage Manager to proxy. Unlike the ObjectCache above, 
    375457this Storage Manager recalls all Units at once upon the first request, 
    376458and 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  
     459for 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 
     475to aggregate multiple stores into a single interface, partitioned by Unit 
     476class. Unlike most other StorageManagers, it takes no options. Instead, 
     477you will generally set this as the root of your storage graph and 
     478repeatedly call its <tt class='def'>add_store(name, store)</tt> method. 
     479There's also a corresponding <tt class='def'>remove_store(name)</tt> 
     480method.</p> 
     481 
     482<p>Once you've added stores, the <tt class='def'>stores</tt> attribute is a 
     483dict from store names to StorageManager instances. However, you shouldn't 
     484manipulate this directly--use add/remove_store instead. When you call 
     485add_store, it will also set up the partitioner's <tt class='def'>classmap</tt> 
     486attribute, which is used to direct queries and other command to the correct 
     487store(s) based on the class. DDL methods will generally dispatch to all 
     488stores for each class. DML methods will generally dispatch to 
     489<tt>classmap[unit.__class__][0]</tt>; those which involve multiple 
     490classes (e.g. multirecall), will try to find a single store which 
     491handles all classes in the given relation. To override this default 
     492search, you can add entries to <tt>classmap</tt> of the form: 
     493<tt>{(clsA, clsB, clsC): [store1]}</tt>, which instructs the 
     494partitioner to use the given store for any Join with the same 
     495order, such as <tt>(clsA &lt;&lt; clsB) &amp; 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> 
    388504 
    389505<a name='comparison'><h3>SM Comparison Chart</h3></a> 
     
    420536    <th>mysql</th> 
    421537    <th>postgres</th> 
    422     <th>ram</th> 
    423     <th>shelve</th> 
    424538    <th>sqlite</th> 
    425539    <th>sqlserver</th> 
     540    <th>ram</th> 
     541    <th>memcached</th> 
     542    <th>shelve</th> 
    426543    <th>folders</th> 
     544    <th>json</th> 
    427545</tr> 
    428546 
     
    433551    <td class='python'>P</td> 
    434552    <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> 
    439559    <td class='notsup'>N</td> 
    440560</tr> 
     
    446566    <td>Y</td> 
    447567    <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> 
    452574    <td class='notsup'>N</td> 
    453575</tr> 
     
    459581    <td>Y</td> 
    460582    <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> 
    465589    <td class='notsup'>N</td> 
    466590</tr> 
     
    472596    <td>64</td> 
    473597    <td>63</td> 
    474     <td class='python'>P</td> 
    475     <td class='python'>P</td> 
    476598    <td>no limit?</td> 
    477599    <td>128</td> 
     600    <td class='python'>P</td> 
     601    <td class='python'>P</td> 
     602    <td class='python'>P</td> 
    478603    <td>OS-dependent</td> 
     604    <td>OS-dependent</td> 
    479605</tr> 
    480606 
     
    489615    <td>Y</td> 
    490616    <td>Y</td> 
     617    <td>Y</td> 
    491618    <td>Y <a href='#filenames'>[3]</a></td> 
     619    <td>Y <a href='#filenames'>[3]</a></td> 
    492620</tr> 
    493621 
     
    498626    <td>Y</td> 
    499627    <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> 
    503633    <td class='python'>P</td> 
    504634    <td class='python'>P</td> 
     
    511641    <td>Y</td> 
    512642    <td>Y</td> 
    513     <td class='python'>P</td> 
    514     <td class='python'>P</td> 
    515643    <td>Y</td> 
    516644    <td><tt>&lt;, &lt;=, ==, !=, &gt;, &gt;=</tt></td> 
    517645    <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> 
    518650</tr> 
    519651 
     
    524656    <td>Y</td> 
    525657    <td>Y</td> 
    526     <td class='python'>P</td> 
    527     <td class='python'>P</td> 
    528658    <td>3.0.8+</td> 
    529659    <td>Y</td> 
    530660    <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> 
    531665</tr> 
    532666 
     
    537671    <td>Y</td> 
    538672    <td>Y</td> 
    539     <td class='python'>P</td> 
    540     <td class='python'>P</td> 
    541673    <td>3.1.0+</td> 
    542674    <td>Y</td> 
    543675    <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> 
    544680</tr> 
    545681 
     
    550686    <td>Y</td> 
    551687    <td>Y</td> 
    552     <td>Y</td> 
    553     <td>Y</td> 
    554688    <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> 
    555693    <td>Y</td> 
    556694    <td>Y</td> 
     
    563701    <th>mysql</th> 
    564702    <th>postgres</th> 
    565     <th>ram</th> 
    566     <th>shelve</th> 
    567703    <th>sqlite</th> 
    568704    <th>sqlserver</th> 
     705    <th>ram</th> 
     706    <th>memcached</th> 
     707    <th>shelve</th> 
    569708    <th>folders</th> 
     709    <th>json</th> 
    570710</tr> 
    571711 
     
    576716    <td>16</td> 
    577717    <td>1000</td> 
    578     <td class='python'>P (pickle)</td> 
    579     <td class='python'>P (pickle)</td> 
    580718    <td>0 (always uses TEXT instead)</td> 
    581719    <td>12</td> 
    582720    <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> 
    583725</tr> 
    584726 
     
    589731    <td>8000 (row limit)</td> 
    590732    <td>1 GB?</td> 
    591     <td class='python'>P (pickle)</td> 
    592     <td class='python'>P (pickle)</td> 
    593733    <td>1 MB (row limit)</td> 
    594734    <td>8000 <a href='#ntext-bytes'>[4]</a></td> 
    595735    <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> 
    596740</tr> 
    597741 
     
    602746    <td>1000-01-01 00:00:00 to 9999-12-31 23:59:59</td> 
    603747    <td>4713 BC to 5874897 AD</td> 
    604     <td class='python'>P</td> 
    605     <td class='python'>P</td> 
    606748    <td>4714-11-24 BC to ???</td> 
    607749    <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> 
    608754    <td class='python'>P</td> 
    609755</tr> 
     
    615761    <td>1 second</td> 
    616762    <td>1 microsecond</td> 
    617     <td class='python'>P</td> 
    618     <td class='python'>P</td> 
    619763    <td>1 second</td> 
    620764    <td>1 second</td> 
    621765    <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> 
    622770</tr> 
    623771 
     
    628776    <td>Y</td> 
    629777    <td>Y</td> 
    630     <td class='python'>P</td> 
    631     <td class='python'>P</td> 
    632778    <td>3.2.3+ <a href='#perfect-dates'>[1]</a></td> 
    633779    <td>Y</td> 
    634780    <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> 
    635785</tr> 
    636786 
     
    641791    <td>Y</td> 
    642792    <td>Y</td> 
    643     <td class='python'>P</td> 
    644     <td class='python'>P</td> 
    645793    <td>3.2.3+ <a href='#perfect-dates'>[1]</a></td> 
    646794    <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> 
    647799    <td class='python'>P</td> 
    648800</tr> 
     
    656808    <td>Y</td> 
    657809    <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> 
    662816    <td class='python'>P</td> 
    663817</tr> 
     
    669823    <td>Y</td> 
    670824    <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> 
    675831    <td class='python'>P</td> 
    676832</tr> 
     
    682838    <th>mysql</th> 
    683839    <th>postgres</th> 
    684     <th>ram</th> 
    685     <th>shelve</th> 
    686840    <th>sqlite</th> 
    687841    <th>sqlserver</th> 
     842    <th>ram</th> 
     843    <th>memcached</th> 
     844    <th>shelve</th> 
    688845    <th>folders</th> 
     846    <th>json</th> 
    689847</tr> 
    690848 
     
    695853    <td>Y</td> 
    696854    <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> 
    701861    <td></td> 
    702862</tr> 
     
    708868    <td>Y</td> 
    709869    <td>Y</td> 
    710     <td></td> 
    711     <td></td> 
    712870    <td class='notsup'>N</td> 
    713871    <td>Y (timeout)</td> 
     872    <td></td> 
     873    <td></td> 
     874    <td></td> 
     875    <td></td> 
    714876    <td></td> 
    715877</tr> 
     
    720882    <td class='notsup'>N <a href='#too-isolated'>[7]</a></td> 
    721883    <td class='notsup'>N <a href='#too-isolated'>[7]</a></td> 
    722     <td></td> 
    723     <td></td> 
    724884    <td class='notsup'>N</td> 
    725885    <td>Y (timeout)</td> 
    726886    <td></td> 
     887    <td></td> 
     888    <td></td> 
     889    <td></td> 
     890    <td></td> 
    727891</tr> 
    728892<tr> 
     
    732896    <td>Y (timeout)</td> 
    733897    <td>Y</td> 
    734     <td></td> 
    735     <td></td> 
    736898    <td>Y <a href='#memory-trans'>[8]</a></td> 
    737899    <td>Y (timeout)</td> 
    738900    <td></td> 
     901    <td></td> 
     902    <td></td> 
     903    <td></td> 
     904    <td></td> 
    739905</tr> 
    740906<tr> 
     
    744910    <td>Y</td> 
    745911    <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> 
    750918    <td></td> 
    751919</tr> 
  • trunk/dejavu/engines.py

    r542 r543  
    324324    def snapshots(self): 
    325325        """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)
    327327        allSnap.sort(dejavu.sort(u'Timestamp')) 
    328328        return allSnap 
  • trunk/dejavu/sandboxes.py

    r542 r543  
    6868                    for arg, key in zip(args, uniq): 
    6969                        kwargs[str(key)] = arg 
    70                     return self.unit(cls, logic.filter(**kwargs)
     70                    return self.unit(cls, **kwargs
    7171                recaller.__doc__ = "A single %s Unit, else None." % name 
    7272                return recaller 
     
    117117            unit.sandbox = None 
    118118     
    119     def xrecall(self, classes, expr=None, inherit=False, **kwargs): 
     119    def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): 
    120120        """Iterator over units of the given class(es) which match expr. 
    121          
    122         If inherit is True, units of the given class and all registered 
    123         subclasses of the given class will be recalled. 
    124121         
    125122        If the 'classes' arg is a UnitJoin, each yielded value will 
     
    142139        """ 
    143140        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): 
    145143                yield unitrow 
    146144            return 
    147145         
    148146        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 
    169157                    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: 
    185161                    # Do NOT call on_recall here. That should be called 
    186162                    # only at the Sandbox-SM boundary. 
    187163                    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): 
    203192                id = unit.identity() 
    204193                # Don't offer up a unit that was already checked in our cache 
     
    223212                        yield unit 
    224213     
    225     def recall(self, classes, expr=None, inherit=False, **kwargs): 
     214    def recall(self, classes, expr=None, order=None, limit=None, offset=None): 
    226215        """List of units of the given class(es) which match expr. 
    227          
    228         If inherit is True, units of the given class and all registered 
    229         subclasses of the given class will be recalled. 
    230216         
    231217        If the 'classes' arg is a UnitJoin, each yielded value will 
     
    247233                multiple classes. 
    248234        """ 
    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        """ 
    267244        cache = self._cache(cls) 
    268         fullexpr = logic.combine(expr, kwargs) 
    269245         
    270246        # Special-case the scenario where one Unit is expected 
    271247        # 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): 
    280249            ident = tuple([kwargs[k] for k in cls.identifiers]) 
    281         if ident: 
    282             unit = cache.get(ident) 
    283             if unit is None: 
    284                 unit = self.store.unit(cls, expr=expr, **kwargs) 
    285                 if unit is not None: 
    286                     unit.sandbox = self 
    287                     cache[unit.identity()] = unit 
    288                     if hasattr(unit, 'on_recall'): 
    289                         try: 
    290                             unit.on_recall() 
    291                         except errors.UnrecallableError: 
    292                             return None 
    293             return unit 
     250            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 
    294263         
    295264        # Query the cache. We have to use a static copy of the 
     
    298267        keys = cache.keys() 
    299268        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 
    305278         
    306279        # Query Storage. 
    307         unit = self.store.unit(cls, expr=expr, **kwargs) 
    308         if unit is not None: 
    309             unit.sandbox = self 
     280        u = self.store.unit(cls, **kwargs) 
     281        if u is not None: 
     282            u.sandbox = self 
    310283             
    311284            # Classes with no identifiers cannot be compared to our cache 
     
    315288                # (even between our cache lookups and this lookup). 
    316289                # Make sure the cache lookup and get happens atomically. 
    317                 id = unit.identity() 
     290                id = u.identity() 
    318291                existing = cache.get(id) 
    319292                if existing: 
    320293                    return existing 
    321                 cache[id] = unit 
    322              
    323             if hasattr(unit, 'on_recall'): 
     294                cache[id] = u 
     295             
     296            if hasattr(u, 'on_recall'): 
    324297                try: 
    325                     unit.on_recall() 
     298                    u.on_recall() 
    326299                except errors.UnrecallableError: 
    327300                    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): 
    331305        """Recall units of each cls if they together match the expr. 
    332306         
     
    349323                multiple classes. 
    350324        """ 
    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) 
    354327         
    355328        # This is broken. If a filter expr is supplied, then the store may 
     
    357330        # in the resultset. If you're using xmulti with no expr's, or 
    358331        # 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): 
    361335            confirmed = True 
    362336            for index in xrange(len(unitset)): 
    363337                unit = unitset[index] 
    364338                id = unit.identity() 
     339                if not unit.sequencer.valid_id(id): 
     340                    # This is a 'dummy unit' from an outer join. 
     341                    continue 
    365342                cache = self._cache(unit.__class__) 
    366343                if id in cache: 
     
    453430        return [x for x in self.xview(query, distinct=distinct)] 
    454431     
    455     def sum(self, cls, attr, expr=None, **kwargs): 
     432    def sum(self, cls, attr, expr=None): 
    456433        """Sum of all non-None values for the given cls.attr.""" 
    457434        expr = logic.Expression(lambda x: getattr(x, attr) != None) + expr 
    458435        return sum([row[0] for row in 
    459                     self.view(dejavu.Query(cls, (attr,), expr, **kwargs))]) 
     436                    self.view(dejavu.Query(cls, (attr,), expr))]) 
    460437     
    461438    def count(self, cls, expr=None): 
     
    467444        return len(self.view((cls, uniq, expr), distinct=True)) 
    468445     
    469     def range(self, cls, attr, expr=None, **kwargs): 
     446    def range(self, cls, attr, expr=None): 
    470447        """Distinct, non-None attr values (ordered and continuous, if possible). 
    471448         
     
    482459        (sorted, if possible). 
    483460        """ 
    484         query = dejavu.Query(cls, [attr], expr, **kwargs
     461        query = dejavu.Query(cls, [attr], expr
    485462        existing = [x[0] for x in self.view(query, distinct=True) 
    486463                    if x is not None] 
  • trunk/dejavu/storage/__init__.py

    r542 r543  
    255255                                        limit=limit, offset=offset)] 
    256256     
    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        """ 
    268263        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() 
    273265        except StopIteration: 
    274266            return None 
     
    438430    def _xmultirecall(self, classes, expr=None, order=None, limit=None, offset=None): 
    439431        """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
    442434         
    443435        # TODO: deconstruct expr into a set of subexpr's, one for 
     
    573565        return len(self.view((cls, uniq, expr), distinct=True)) 
    574566     
    575     def range(self, cls, attr, expr=None, **kwargs): 
     567    def range(self, cls, attr, expr=None): 
    576568        """Distinct, non-None attr values (ordered and continuous, if possible). 
    577569         
     
    588580        (sorted, if possible). 
    589581        """ 
    590         query = dejavu.Query(cls, [attr], expr, **kwargs
     582        query = dejavu.Query(cls, [attr], expr
    591583        existing = [x[0] for x in self.xview(query, distinct=True) 
    592584                    if x is not None] 
     
    611603         
    612604        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))]) 
    613611     
    614612    #                            Transactions                             # 
  • trunk/dejavu/storage/caching.py

    r542 r543  
    4242            self.cache = resolve("ram") 
    4343     
    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) 
    7252            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 
    7560     
    7661    def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): 
  • trunk/dejavu/storage/storefs.py

    r542 r543  
    9898        return unitdict 
    9999     
    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): 
    118107            # Looking up a Unit by its identifiers. 
    119108            # Skip walking the shelf. 
    120109            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)) 
    123111             
    124112            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]) 
    127115            if not folder: 
    128116                folder = "__blank__" 
     
    136124                return None 
    137125         
    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) 
    143127     
    144128    def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): 
  • trunk/dejavu/storage/storejson.py

    r542 r543  
    9191        return self.decoder.decode(v) 
    9292     
    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): 
    111100            # Looking up a Unit by its identifiers. 
    112101            # Skip walking the shelf. 
    113102            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)) 
    116104             
    117105            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]) 
    120108            if not fname: 
    121109                fname = "__blank__" 
     
    130118            return unit 
    131119         
    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) 
    137121     
    138122    def xrecall(self, classes, expr=None, order=None, limit=None, offset=None): 
  • trunk/dejavu/storage/storememcached.py

    r542 r543  
    7575                             md5.new(pickle.dumps(ident)).hexdigest()) 
    7676     
    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        """ 
    9583        if self.logflags & logflags.RECALL: 
    96             self.log(logflags.RECALL.message(cls, fullexpr)) 
    97          
    98         if (expr is None) and set(kwargs.keys()) == set(cls.identifiers): 
     84            self.log(logflags.RECALL.message(cls, kwargs)) 
     85         
     86        if set(kwargs.keys()) == set(cls.identifiers): 
    9987            # Looking up a Unit by its identifiers. 
    10088            # Skip grabbing the cached class index (a HUGE optimization). 
    10189            key = tuple([kwargs[k] for k in cls.identifiers]) 
    10290            key = "%s:%s:%s" % (self.name, cls.__name__, 
    103                                 md5.new(pickle.dumps(key)).hexdigest()) 
     91                                md5.new(pickle.dumps(key)).hexdigest()) 
    10492            unit = self.client.get(key) 
    10593            if unit is not None: 
     
    11098            keys = self._get_class_index(cls) or set() 
    11199            try: 
    112                 return self._xrecall_inner(keys, fullexpr).next()[0] 
     100                expr = logic.filter(**kwargs) 
     101                return self._xrecall_inner(keys, expr).next()[0] 
    113102            except StopIteration: 
    114103                return None 
  • trunk/dejavu/storage/storeram.py

    r542 r543  
    2323        return lock 
    2424     
    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        """ 
    4331        if self.logflags & logflags.RECALL: 
    44             self.log(logflags.RECALL.message(cls, fullexpr)) 
     32            self.log(logflags.RECALL.message(cls, kwargs)) 
    4533         
    4634        lock = self._get_lock(cls) 
     
    4836            cache = self._caches[cls] 
    4937             
    50             if (expr is None) and set(kwargs.keys()) == set(cls.identifiers): 
     38            if set(kwargs.keys()) == set(cls.identifiers): 
    5139                # Looking up a Unit by its identifiers. 
    5240                # Skip grabbing the cached class index (a HUGE optimization). 
     
    6149             
    6250            try: 
    63                 return self._xrecall_inner(cache, cache.keys(), fullexpr 
     51                expr = logic.filter(**kwargs) 
     52                return self._xrecall_inner(cache, cache.keys(), expr 
    6453                                           ).next()[0] 
    6554            except StopIteration: 
  • trunk/dejavu/storage/storeshelve.py

    r542 r543  
    4949        self.locks = {} 
    5050     
    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        """ 
    6957        if self.logflags & logflags.RECALL: 
    70             self.log(logflags.RECALL.message(cls, fullexpr)) 
     58            self.log(logflags.RECALL.message(cls, kwargs)) 
    7159         
    7260        data = self.shelves[cls] 
     
    7462            return None 
    7563         
    76         if (expr is None) and set(kwargs.keys()) == set(cls.identifiers): 
     64        if set(kwargs.keys()) == set(cls.identifiers): 
    7765            # Looking up a Unit by its identifiers. 
    7866            # Skip grabbing the entire shelf (a HUGE optimization). 
     
    9179         
    9280        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] 
    9483        except StopIteration: 
    9584            return None 
  • trunk/dejavu/test/test_dejavu.py

    r542 r543  
    136136        self.assertEqual(bat3.Legs, 4) 
    137137     
    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 visits 
    156               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      
    162138    def test_UnitJoin(self): 
    163139        box = store.new_sandbox() 
     
    194170    def test_json(self): 
    195171        try: 
    196             from dejavu.json import Converter, Encoder, Decoder 
     172            from dejavu.json import * 
    197173        except ImportError: 
    198174            print "JSON funtionality requires the simplejson package." 
    199175            return 
    200176         
    201         json = Converter(store
     177        json = Converter(
    202178        box = store.new_sandbox() 
    203179         
     
    215191                 
    216192        zoo = box.unit(Zoo, Name="San Diego Zoo") 
    217         zoo_json = json.dumps(zoo
     193        zoo_json = json.dumps(unit_to_dict(zoo)
    218194         
    219195        self.assert_("1916-10-02" in zoo_json) 
    220         self.assert_("__dejavu.Unit__" in zoo_json) 
     196        self.assert_("__dejavu.class__" in zoo_json) 
    221197         
    222198        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)
    224200         
    225201        self.assertEqual(zoo2.Name, "The San Diego Zoo") 
  • trunk/dejavu/test/zoo_fixture.py

    r542 r543  
    466466                # We flush_all to ensure a DB hit each time. 
    467467                box.flush_all() 
    468                 return len(box.recall(cls, (lam))) 
     468                return len(box.recall(cls, lam)) 
    469469             
    470470            zoos = box.recall(Zoo) 
     
    969969        try: 
    970970            # 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)): 
    972972                firstvisit = box.unit(Visit, VetID=1, Date=Jan_1_2001) 
    973973                self.assertEqual(firstvisit.VetID, 1) 
     
    975975             
    976976            # Test recall inside of xrecall 
    977             for visit in box.xrecall(Visit, VetID=1): 
     977            for visit in box.xrecall(Visit, dict(VetID=1)): 
    978978                f = (lambda x: x.VetID == 1 and x.ID != visit.ID) 
    979979                othervisits = box.recall(Visit, f) 
     
    981981             
    982982            # 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)): 
    984984                # visit.Vet is a ToOne association, so will return a unit or None. 
    985985                vet = visit.Vet() 
     
    991991        box = root.new_sandbox() 
    992992        try: 
    993             quadrupeds = box.recall(Animal, Legs=4
     993            quadrupeds = box.recall(Animal, dict(Legs=4)
    994994            self.assertEqual(len(quadrupeds), 4) 
    995995             
     
    10391039            eng.rules()[-1].forget() 
    10401040            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 visits 
    1063                   if getattr(x, "Topic", None) == "Cage Cleaning"] 
    1064             self.assertEqual(len(cc), 1) 
    1065              
    1066             # Checking for non-existent attributes in/from subclasses 
    1067             # 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) 
    10711041        finally: 
    10721042            box.flush_all() 
     
    15501520    global root 
    15511521    store.register_all(globals()) 
     1522    engines.register_classes(store) 
     1523    dejavu.DeployedVersion.register(store) 
    15521524    if hasattr(store, "cache"): 
    15531525        store.cache.register_all(globals()) 
     1526        engines.register_classes(store.cache) 
     1527        dejavu.DeployedVersion.register(store.cache) 
    15541528    if hasattr(store, "nextstore"): 
    15551529        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) 
    15581532     
    15591533    if mediated: 
  • trunk/dejavu/units.py

    r542 r543  
    1313import warnings 
    1414 
    15 from dejavu import errors 
     15from dejavu import errors, logic 
    1616 
    1717 
     
    7878        raise AttributeError("Unit Associations may not be deleted.") 
    7979     
    80     def related(self, unit, expr=None, **kwargs): 
     80    def related(self, unit, expr=None, order=None, limit=None, offset=None): 
    8181        """Return unit(s) on the far side of this relation.""" 
    8282        raise NotImplementedError 
     
    8888    to_many = False 
    8989     
    90     def related(self, unit, expr=None, **kwargs): 
     90    def related(self, unit, expr=None, order=None, limit=None, offset=None): 
    9191        """Return the single unit on the far side of this relation.""" 
    9292        value = getattr(unit, self.nearKey) 
     
    9494            return None 
    9595         
    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) 
    9899        try: 
    99100            return units.next() 
     
    107108    to_many = True 
    108109     
    109     def related(self, unit, expr=None, **kwargs): 
     110    def related(self, unit, expr=None, order=None, limit=None, offset=None): 
    110111        """Return all units on the far side of this relation.""" 
    111112        value = getattr(unit, self.nearKey) 
     
    113114            return [] 
    114115         
    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) 
    117119 
    118120