Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

Changeset 38

Show
Ignore:
Timestamp:
12/15/04 22:50:08
Author:
fumanchu
Message:

1. Added Unit.first(farClass, **kwargs) method to retrieve a single related Unit.
2. Fixed doc bug in related_units.
3. associate() is now module-level, not a method of arena. Arenas discover associations when each Unit is registered.
4. Added Arena.register_all().
5. Doc updates.
6. Added engines.register_classes(arena) function.
7. Rewrote tests to import common zoo module.

Files:

Legend:

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

    r37 r38  
    8181 
    8282class UnitSequencerNull(object): 
    83     """A null sequencer for Unit IDs. Sequencing will error. 
     83    """UnitSequencerNull(type=unicode). 
     84    A null sequencer for Unit IDs. Sequencing will error. 
    8485     
    8586    In many cases, ID values simply have no algorithmic sequence; 
     
    100101 
    101102class UnitSequencerInteger(object): 
    102     """A sequencer for Unit IDs, where id[i+1] == id[i] + 1.""" 
     103    """UnitSequencerInteger(type=int, initial=1) 
     104    A sequencer for Unit IDs, where id[i+1] == id[i] + 1.""" 
    103105     
    104106    def __init__(self, type=int, initial=1): 
     
    115117 
    116118class UnitSequencerUnicode(object): 
    117     """A sequencer for Unit IDs, where e.g. next(['abc']) == 'abd'.""" 
     119    """UnitSequencerUnicode(type=unicode, width=6, 
     120        range="abcdefghijklmnopqrstuvwxyz") 
     121    A sequencer for Unit IDs, where e.g. next(['abc']) == 'abd'.""" 
    118122     
    119123    def __init__(self, type=unicode, width=6, 
     
    158162 
    159163class UnitProperty(object): 
    160     """Data descriptor for Unit data which will persist in storage. 
     164    """UnitProperty(type=unicode, index=False, hints={}, key=None) 
     165    Data descriptor for Unit data which will persist in storage. 
    161166     
    162167    pre, post: Override these with functions to provide custom behaviors 
     
    180185     
    181186    def __init__(self, type=unicode, index=False, hints={}, key=None): 
    182         self.type = type 
    183187        if type.__name__ == 'FixedPoint': 
    184188            # fixedpoint.Fixedpoint can't be pickled because it 
     
    186190            _define_fixedpoint_states() 
    187191         
     192        self.type = type 
    188193        self.index = index 
    189194        self.hints = hints 
     
    249254 
    250255class Unit(object): 
    251     """A generic object, the building-block of Dejavu. 
    252      
    253     These are purposefully lightweight, relying on Sandboxes to cache 
    254     them, which in turn rely on Storage Managers to load and save them. 
     256    """Unit(**kwarg properties). A generic, persistent object. 
     257     
     258    Units are the building-block of Dejavu. They are purposefully lightweight, 
     259    relying on Sandboxes to cache them, which in turn rely on Storage Managers 
     260    to load and save them. 
    255261     
    256262    They maintain their own "schema" via UnitProperty objects, so that the 
     
    387393        self._properties, self._initial_property_hash = state 
    388394     
     395     
     396    #                        Associations                        # 
     397     
     398    def first(self, farClass, **kwargs): 
     399        """Return the first associated farClass Unit or None. 
     400         
     401        Passes additional kwargs to sandbox.unit(). 
     402        """ 
     403        try: 
     404            key, farKey = self.__class__._associations[farClass] 
     405        except KeyError: 
     406            raise AssociationError("'%s' is not associated with '%s'" 
     407                                   % (self.__class__, farClass)) 
     408         
     409        value = getattr(self, key) 
     410        if value is None: 
     411            return None 
     412         
     413        # kwargs won't take unicode keys 
     414        kwargs[str(farKey)] = value 
     415        return self.sandbox.unit(farClass, **kwargs) 
     416     
    389417    def add(self, unit): 
    390418        """add(unit) -> Auto-create a relationship between self and unit.""" 
     
    407435 
    408436 
     437def relation_factory(key, farClass, farKey): 
     438    """Produce a new recaller method for a Unit subclass.""" 
     439    def related_units(self, expr=None): 
     440        value = getattr(self, key) 
     441        if value is None: 
     442            return iter([]) 
     443         
     444        # kwargs won't take unicode keys 
     445        f = logic.filter(**{str(farKey): value}) 
     446        if expr is not None: 
     447            f += expr 
     448        return self.sandbox.recall(farClass, f) 
     449     
     450    related_units.__doc__ = ( 
     451    """Iterator over '%(farname)s' Units whose %(farkey)s matches self.%(key)s. 
     452    If self.%(key)s is None, no Units will be recalled.""" 
     453    % {'farname': farClass.__name__, 
     454       'farkey': farKey, 
     455       'key': key, 
     456       }) 
     457    return related_units 
     458 
     459def associate(cls, key, farClass, farKey, nearFactory=None, farFactory=None): 
     460    """Associate one Unit class with another by relating attributes. 
     461     
     462    cls, key: The 'near' class and its key. 
     463    farClass, farKey: the 'far' class and its key. 
     464     
     465    Far Units will be recalled if their farKey matches cls.key. 
     466    However, if cls.key is empty or None, no Units will be recalled. 
     467    """ 
     468     
     469    # Add a method to cls which retrieves farClass Units 
     470    if nearFactory is None: 
     471        nearFactory = relation_factory 
     472    func = nearFactory(key, farClass, farKey) 
     473    setattr(cls, farClass.__name__, func) 
     474     
     475    # Add the farClass to the association dictionary of cls. 
     476    cls._associations[farClass] = (key, farKey) 
     477     
     478    # Add a method to farClass which retrieves cls Units 
     479    if farFactory is None: 
     480        farFactory = relation_factory 
     481    func = farFactory(farKey, cls, key) 
     482    setattr(farClass, cls.__name__, func) 
     483     
     484    # Add the cls to the association dictionary of farClass. 
     485    farClass._associations[cls] = (farKey, key) 
     486 
     487 
    409488########################################################################### 
    410489##                                                                       ## 
     
    415494 
    416495class Arena(object): 
    417     """A namespace/workspace for a Dejavu application.""" 
     496    """Arena(). A namespace/workspace for a Dejavu application.""" 
    418497     
    419498    def __init__(self): 
     
    485564            # We left cls == None in _load(). Set it now. 
    486565            self.roster.facets['cls'][row] = cls 
     566         
     567        # Register any association(s) in an undirected graph. 
     568        for farClass in cls._associations: 
     569            self.associations.connect(cls, farClass) 
     570     
     571    def register_all(self, globals): 
     572        for obj in globals.itervalues(): 
     573            if isinstance(obj, type) and issubclass(obj, Unit): 
     574                self.register(obj) 
    487575     
    488576    def class_by_name(self, classname): 
     
    494582            s = self.defaultStore 
    495583            if s is None: 
    496                 raise DejavuError("No storage for class '%s'" % cls.__name__) 
     584                raise DejavuError("No storage defined for class '%s'" % 
     585                                  cls.__name__) 
    497586        return s 
    498587     
    499588    def create_storage(self, cls): 
    500589        self.storage(cls).create_storage(cls) 
    501      
    502     def associate(self, cls, key, farClass, farKey, 
    503                   nearFactory=None, farFactory=None): 
    504         """Associate one Unit class with another by relating attributes. 
    505          
    506         cls, key: The 'near' class and its key. 
    507         farClass, farKey: the 'far' class and its key. 
    508          
    509         Far Units will be recalled if their farKey matches cls.key. 
    510         However, if cls.key is empty or None, no Units will be recalled. 
    511         """ 
    512          
    513         # Disallow overwriting of existing attributes. 
    514         if hasattr(cls, farClass.__name__): 
    515             raise AssociationError(u"%s already has an attribute named '%s'." 
    516                                    % (cls, farClass.__name__)) 
    517         if hasattr(farClass, cls.__name__): 
    518             raise AssociationError(u"%s already has an attribute named '%s'." 
    519                                    % (farClass, cls.__name__)) 
    520          
    521         # Assert that both classes are registered. 
    522         self.register(cls) 
    523         self.register(farClass) 
    524          
    525         # Add a method to cls which retrieves farClass synapses 
    526         if nearFactory is None: 
    527             nearFactory = _synapses_func 
    528         func = nearFactory(key, farClass, farKey) 
    529         setattr(cls, farClass.__name__, func) 
    530         cls._associations[farClass] = (key, farKey) 
    531          
    532         # Add a method to farClass which retrieves cls synapses 
    533         if farFactory is None: 
    534             farFactory = _synapses_func 
    535         func = farFactory(farKey, cls, key) 
    536         setattr(farClass, cls.__name__, func) 
    537         farClass._associations[cls] = (farKey, key) 
    538          
    539         # Register the association(s) in an undirected graph. 
    540         self.associations.connect(cls, farClass) 
    541590 
    542591 
     
    544593# process. Otherwise, you should create your own instance per application. 
    545594dejavuarena = Arena() 
    546  
    547 def _synapses_func(key, farClass, farKey): 
    548     """Produce a new synapses() function to be bound to a Unit class.""" 
    549     def synapses(self, expr=None): 
    550         """Recall associated '%(farname)s' Units. 
    551          
    552         %(farname)s Units will be recalled if their %(farkey)s matches 
    553         self.%(key)s. However, if self.%(key)s is None, 
    554         no Units will be recalled. 
    555         """ % {'farname': farClass.__name__, 
    556                'farkey': farKey, 
    557                'key': key, 
    558                } 
    559         value = getattr(self, key) 
    560         if value is None: 
    561             return iter([]) 
    562          
    563         # kwargs won't take unicode keys 
    564         f = logic.filter(**{str(farKey): value}) 
    565         if expr is not None: 
    566             f += expr 
    567         return self.sandbox.recall(farClass, f) 
    568     return synapses 
    569595 
    570596 
     
    577603 
    578604class Sandbox(object): 
    579     """Data sandbox for Dejavu arenas. 
     605    """Sandbox(arena). Data sandbox for Dejavu arenas. 
    580606     
    581607    Each consumer (that is, each UI process) maintains a Sandbox for 
  • trunk/doc/dejavu.css

    r37 r38  
    6464} 
    6565 
     66pre { 
     67    margin-left: 3em; 
     68} 
     69 
    6670/*, table { border: 2px dotted red } 
  • trunk/doc/framework.html

    r37 r38  
    3737creating a custom Storage Manager, whether for a new backend, or a custom 
    3838middleware component. Storage Managers must conform to a simple interface 
    39 for creating, destroying, and recalling Units. Beyond that, they are free 
    40 to do whatever they like. 
    41 </p> 
     39for creating, destroying, and recalling Units. They are free to implement 
     40that functionality however they like.</p> 
    4241 
    4342<p>As you can see in the code, the <tt>storage.StorageManager</tt> base 
     
    5453        not need to be fully populated. If the Unit has an ID when 
    5554        passed to <tt>reserve</tt>, use it. If not, supply it using 
    56         the class' UnitSequencer. You are not required to peris
    57         any Unit attributes other than UnitProperties.</li> 
     55        the class' UnitSequencer. If your database provides equivalen
     56        sequencing to dejavu Sequencers, feel free to use it.</li> 
    5857    <li><tt>save(self, unit, forceSave=False):</tt> Required. Take the 
    59         supplied Unit instance and persist its properties to storage.</li> 
     58        supplied Unit instance and persist its properties to storage. 
     59        You are not required to persist any Unit attributes other than 
     60        UnitProperties.</li> 
    6061    <li><tt>destroy(self, unit):</tt> Required. Remove the Unit's data 
    6162        from storage permanently.</li> 
     
    217218 
    218219<h4>Other Serialization Mechanisms</h4> 
    219 <h5>shelve</h5> 
    220 <p>Extremely simple implementation. Everything is pickled. Querying will 
    221 be slow--every Unit is sucked in one-by-one and tested in pure Python 
    222 using <tt>Expression.evaluate()</tt>. But for many applications, you don't 
    223 need heavyweight query tools; for example, an online forum may only need 
    224 topic content looked up by ID. Or system tables that only get read at 
    225 startup might benefit.</p> 
    226  
    227220<h5>sockets</h5> 
    228221<p>There's a <tt>sockets</tt> module in the <tt>storage</tt> package. 
     
    231224with a third-party database, which couldn't handle web-traffic threading 
    232225models. Here's a snippet of how to use it (from that app): 
    233 <pre>    def query(self, cmd, unitType='', data=None): 
    234         if isinstance(data, dejavu.Unit): 
    235             data = stream(data) 
    236         elif data is None: 
    237             data = '' 
    238         else: 
    239             data = pickle.dumps(data) 
    240         response = self.socket.query(":".join((cmd, unitType, data))) 
    241         return response 
     226<pre>def query(self, cmd, unitType='', data=None): 
     227    if isinstance(data, dejavu.Unit): 
     228        data = stream(data) 
     229    elif data is None: 
     230        data = '' 
     231    else: 
     232        data = pickle.dumps(data) 
     233    response = self.socket.query(":".join((cmd, unitType, data))) 
     234    return response 
    242235</pre> 
    243236</p> 
  • trunk/doc/index.html

    r37 r38  
    2828        <ul> 
    2929        <li>UnitProperty</li> 
     30        <li>Unit ID's</li> 
    3031        <li>Creating and Populating Properties</li> 
    3132        <li>Unit Properties are First-Class Objects</li> 
    3233        <li>Triggers</li> 
    33         <li>Unit ID's</li> 
    3434        <li>Registration of Unit Classes</li> 
    3535        </ul> 
     
    5656        </ul> 
    5757    </li> 
    58     <li>Associations between Unit Classes</li> 
     58    <li>Associations between Unit Classes 
     59        <ul> 
     60        <li><tt>related_units</tt> methods</li> 
     61        <li><tt>Unit.first()</tt></li> 
     62        </ul> 
     63    </li> 
    5964    <li>Unit Engines 
    6065        <ul> 
     
    8590            <li>Microsoft SQL Server</li> 
    8691            <li>ODBC</li> 
     92            <li>Shelve</li> 
    8793            </ul> 
    8894        </li> 
  • trunk/doc/intro.html

    r37 r38  
    7272from dejavu.storage.storeado import StorageManagerADO_MSAccess as SM 
    7373 
    74 # Set up a global Arena object. 
    75 arena = dejavu.Arena() 
    76 conf = {u'Connect': 
    77         r"PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=C:\zookeeper.mdb;"} 
    78 arena.add_store('main', SM("mySM", arena, conf)) 
    79  
    8074 
    8175class Zoo(dejavu.Unit): 
     
    9185                        "ZooID": int, 
    9286                        }) 
    93 arena.associate(Zoo, 'ID', Animal, 'ZooID')</pre> 
     87dejavu.associate(Zoo, 'ID', Animal, 'ZooID') 
     88 
     89# Set up a global Arena object. 
     90arena = dejavu.Arena() 
     91conf = {u'Connect': 
     92        r"PROVIDER=MICROSOFT.JET.OLEDB.4.0;DATA SOURCE=C:\zookeeper.mdb;"} 
     93arena.add_store('main', SM("mySM", arena, conf)) 
     94arena.register_all(globals())</pre> 
    9495 
    9596<p>The above creates the model/schema for the zookeeper application. 
     
    101102            methods of setting Unit properties. Each class also inherits 
    102103            an 'ID' property (an int) from <tt>dejavu.Unit</tt>.</li> 
     104        <li>The association between the Zoo class and the Animal class.</li> 
    103105        <li>The setup of a dejavu <tt>Arena</tt> object, including a Storage 
    104106            Manager which uses a Microsoft Access (Jet) database.</li> 
    105         <li>The association between the Zoo class and the Animal class.</li> 
    106107    </ol> 
    107108</p> 
     
    174175 
    175176<p>You can obtain Dejavu from its Subversion repository at 
    176 <tt>svn://casadeamor.com/dejavu/trunk</tt>. Dejavu expects to be 
    177 installed in <tt>site-packages/dejavu</tt>.</p> 
     177<tt>svn://casadeamor.com/dejavu/trunk</tt>. Dejavu uses no relative imports, 
     178and therefore expects to be installed in <tt>site-packages/dejavu</tt> 
     179or some other root python path.</p> 
    178180 
    179181<p>Dejavu depends upon two additional libraries, <tt>recur.py</tt> and 
    180 <tt>xray.py</tt>, which are available at <tt>svn://casadeamor.com/misc</tt>. 
    181 You need to place these two modules in your site-packages directory.</p> 
     182<tt>xray.py</tt>, which are available at <tt>svn://casadeamor.com/misc/trunk</tt>. 
     183You need to place these two modules in your python path, usually 
     184site-packages.</p> 
    182185 
    183186<p>Dejavu was built using Python 2.3.2. You should probably use 
     
    192195<h3>Compared To Other Database Wrappers</h3> 
    193196<h4>SQLObject</h4> 
    194 <p>See http://blog.colorstudy.com/ianb/weblog/2004/09/06.html#P154 
     197<p>No matter what project I start on, odds are I'll discover that Ian 
     198Bicking has already done the same thing, usually better. 
     199<br />See http://blog.ianbicking.org/another-less-sleepy-alternative-to-hibernate.html 
    195200<br />Which was a reply to Ruby's ActiveRecord: 
    196201http://www.loudthinking.com/arc/000297.html 
     
    235240    bookID = UnitProperty(int) 
    236241 
     242dejavu.associate(Book, 'publisher', Publisher, 'ID') 
     243dejavu.associate(Authorship, 'bookID', Book, 'ID') 
     244dejavu.associate(Authorship, 'authorID', Author, 'ID') 
     245 
    237246arena = Arena() 
    238 arena.associate(Book, 'publisher', Publisher, 'ID') 
    239 arena.associate(Authorship, 'bookID', Book, 'ID') 
    240 arena.associate(Authorship, 'authorID', Author, 'ID') 
     247arena.register_all(globals()) 
    241248</pre> 
    242249 
     
    276283 
    277284ppython.publisher = oreilly.ID 
    278 print ppython.Publisher().next().name # output: "O'Reilly" 
     285print ppython.first(Publisher).name # output: "O'Reilly" 
    279286 
    280287print len([x for x in oreilly.Book()]) # output: 1 
    281288 
    282 print 'Hi,', oreilly.Book().next().author_names() # output: "Hi, Mark Lutz" 
     289print 'Hi,', oreilly.first(Book).author_names() # output: "Hi, Mark Lutz" 
    283290</pre> 
    284291</p> 
  • trunk/doc/modeling.html

    r37 r38  
    1414 
    1515<h3>Units</h3> 
    16 <p>When constructing a Domain Model for your application, you need 
    17 to have a distinction between data that will be persisted and data that 
    18 will not. At the most general level, you might say that some entire 
    19 <i>objects</i> need to be persisted. By registering a subclass of 
    20 <tt>dejavu.Unit</tt>, you specify a set of objects (instances of your 
    21 class) which will be persisted.</p> 
     16<p>When constructing a Domain Model for your application, you will want 
     17to distinguish between objects that will be persisted and objects that 
     18will not. By registering a subclass of <tt>dejavu.Unit</tt>, you allow 
     19instances of that subclass to be persisted.</p> 
    2220 
    2321<p>Before you can register your Unit class, you must create it: 
     
    4644This adds three persistent attributes to our <tt>Printer</tt> objects, 
    4745each with a different datatype. In addition, every subclass of <tt>Unit</tt> 
    48 inherits an 'ID' attribute, an int.</p> 
     46inherits an 'ID' property, an int.</p> 
    4947 
    5048<p>When you get and set <tt>UnitProperty</tt> attributes, they behave just 
     
    5654However, you will notice right away that the int value we provided has been 
    5755coerced to a float behind the scenes. This is because we specified the PPM 
    58 attribute as a 'float' type when we created it. Unit Properties are 
    59 restricted to the types which you specify. The only other valid value 
    60 for a Unit Property is None; any Property may be None at any time, and 
    61 in fact, all Properties are None until you assign values to them: 
     56attribute as a 'float' type when we created it. The value of a Unit 
     57Property is restricted to the type which you specify. The only other valid 
     58value for a Unit Property is None; any Property may be None at any time, 
     59and in fact, all Properties are None until you assign values to them: 
    6260<pre>>>> p.ColorCopies is None 
    6361True</pre></p> 
     62 
     63<h4>Unit ID's</h4> 
     64<p>The <tt>Unit</tt> base class possesses a single Unit Property, an int 
     65named 'ID'. If you wish to use ID's of a different type, simply override 
     66the ID attribute in your subclass: 
     67<pre>class Printer(Unit): 
     68    ID = UnitProperty(unicode)</pre> 
     69Every Unit must possess an ID property. This ensures that each Unit within 
     70the system is unique.</p> 
    6471 
    6572<h4>Creating and Populating Properties</h4> 
     
    124131where 0 implies no limit.</p> 
    125132 
     133<p>When you define a UnitProperty instance, you can pass in these extra 
     134attributes. The signature for UnitProperty is <tt>(type=unicode, 
     135index=False, hints={}, key=None)</tt>. Supply any, all, or none of them 
     136as needed.</p> 
     137 
    126138<h4>Triggers</h4> 
    127139<p>In addition, each UnitProperty has a <tt>pre</tt> and <tt>post</tt> 
     
    133145    def post(self, unit, value): 
    134146        unit.Date = datetime.datetime.now().replace(microsecond=0) 
     147        parent = unit.first(Forum) 
     148        if parent: 
     149            parent.Date = unit.Date 
    135150 
    136151class Topic(Unit): 
    137152    Date = UnitProperty(datetime.date) 
    138     Content = DatedProperty()</pre> 
     153    Content = DatedProperty() 
     154    ForumID = UnitProperty(int) 
     155 
     156class Forum(Unit): 
     157    Date = UnitProperty(datetime.date) 
     158 
     159associate(Topic, 'ForumID', Forum, 'ID')</pre> 
    139160In this example, whenever Topic().Content is set, the <tt>post</tt> 
    140161method will be called and the object's <tt>Date</tt> attribute will 
    141 be modified.</p> 
    142  
    143 <h4>Unit ID's</h4> 
    144 <p>The <tt>Unit</tt> base class possesses a single Unit Property, an int 
    145 named 'ID'. If you wish to use ID's of a different type, simply override 
    146 the ID attribute in your subclass: 
    147 <pre>class Printer(Unit): 
    148     ID = UnitProperty(unicode)</pre> 
    149 Every Unit must possess an ID property. This ensures that each Unit within 
    150 the system is unique.</p> 
     162be modified. Then, the Topic's parent Forum is looked up and <i>its</i> 
     163<tt>Date</tt> is modified.</p> 
     164 
     165<p>As with any trigger system, you need to be careful not to have triggers 
     166called out of order. For example, if a user changes both the ForumID and 
     167Content properties in a single operation (like a web page submit), the old 
     168Forum will be incorrectly modified if the Content property is applied 
     169first. I don't have any cool tools built into Dejavu to help you with 
     170this, but I'm open to suggestions.</p> 
    151171 
    152172<h4>Registration of Unit Classes</h4> 
     
    154174class with your application's <tt>Arena</tt> object. Each class which 
    155175you want Dejavu to manage must be passed to <tt>Arena.register(cls)</tt>. 
    156 </p> 
     176If you create a module with multiple classes, you can register them all 
     177at once with <tt>Arena.register_all(globals())</tt>. It will grab any 
     178Unit subclasses out of your module's globals() (or any other mapping 
     179you pass to <tt>register_all</tt>) and register them.</p> 
    157180 
    158181<h3>Sandboxes</h3> 
     
    294317<h4>Flushing Sandboxes</h4> 
    295318<p>When the client connection has closed, you should <i>flush</i> the 
    296 Sandbox caches. In general, a single call to <tt>flush_all()</tt> will do 
    297 the trick. Notice that flushing calls <tt>repress()</tt> for each Unit in 
    298 the Sandbox, and any <tt>on_repress()</tt> triggers will be executed.</p> 
     319Sandbox caches. In general, a single call to <tt>Sandbox().flush_all()</tt> 
     320will do the trick. Notice that flushing calls <tt>repress()</tt> for each 
     321Unit in the Sandbox, and any <tt>on_repress()</tt> triggers will be 
     322executed.</p> 
    299323 
    300324 
     
    317341a given class. In Dejavu, you filter the set according to the UnitProperty 
    318342attributes for each object. Naturally, there must be a way to express 
    319 the filter you intend. Dejavu actually provides three ways: Expressions, 
     343the filter you intend. Dejavu actually provides three ways, all in the 
     344<tt>dejavu.logic</tt> module: <tt>Expression</tt>, 
    320345<tt>filter</tt>, and <tt>comparison</tt>.</p> 
    321346 
     
    336361always be bound to a Unit instance. In the example above, it's named 'x', 
    337362but you can use any name you like. Using lambdas as a base means that we 
    338 can simply call Expression.func(Unit), and receive a boolean value 
    339 indicating whether our Unit "passes the test". Attribute lookups on our 
    340 'x' object will apply to Unit Properties for that Unit object. 
    341 That is, <tt>x.Date</tt> becomes <tt>Unit.Date</tt>.</p> 
     363can simply call <tt>Expression.evaluate(unit)</tt>, and receive a boolean 
     364value indicating whether our Unit "passes the test". Attribute lookups on 
     365our 'x' object will apply to Unit Properties for that Unit object. 
     366That is, <tt>x.Date</tt> becomes <tt>unit.Date</tt>.</p> 
    342367 
    343368<h4>Early binding</h4> 
     
    547572<p>You could stop at this point in your design, and simply remember what 
    548573these keys are and how they relate, and manipulate them accordingly. But 
    549 Dejavu allows you to <i>register</i> these associations explicitly in your 
    550 <tt>Arena</tt>: 
    551 <pre>myArena.associate(Archaeologist, 'ID', Biography, 'ArchID')</pre> 
     574Dejavu allows you to explicitly declare these associations: 
     575<pre>dejavu.associate(Archaeologist, 'ID', Biography, 'ArchID')</pre> 
    552576You pass in the near class, the near key, the far class, and the far key. 
    553577</p> 
    554578 
    555 <p>What does an explicit association buy for you? First, the <tt>associate</tt> 
    556 call adds an entry in the <tt>Arena.associations</tt> registry, so that 
    557 smart consumer code (like Unit Engine Rules, below) can automatically 
    558 follow association paths for you. Second, each Unit class has a private 
    559 <tt>_associations</tt> attribute, a <tt>dict</tt>. Each Unit involved 
    560 in the association gains an entry in that dict: the key is the far class 
    561 itself (not the class name), and the value is a tuple of (far key, near key). 
    562 Third, <tt>associate()</tt> can be used to register your Unit classes in 
    563 the Arena's <tt>roster</tt>; you don't have to call <tt>register</tt> for 
    564 either class if you call <tt>associate</tt> (see <u>The Arena Object</u>, 
    565 below).</p> 
    566  
    567 <p>In addition, each of the Unit classes will gain a new <i>synapse</i> 
    568 method which simplifies looking up related instances of the other class. 
    569 The new method for Unit_B will have the name of Unit_A, and vice-versa. 
    570 In our example: 
     579<p>What does an explicit association buy for you? First, Arenas discover them 
     580and fill the <tt>Arena.associations</tt> registry, so that smart consumer 
     581code (like Unit Engine Rules, below) can automatically follow association 
     582paths for you. Second, each Unit class has a private <tt>_associations</tt> 
     583attribute, a <tt>dict</tt>. Each Unit involved in in the association gains 
     584an entry in that dict: the key is the far class itself (not the class name), 
     585and the value is a tuple of (near key, far key).</p> 
     586 
     587<h4><tt>related_units</tt> methods</h4> 
     588<p>In addition, each of the two Unit classes will gain a new 
     589<i>related_units</i> method which simplifies looking up related instances 
     590of the other class. The new method for Unit_B will have the name of Unit_A, 
     591and vice-versa. In our example: 
    571592<pre>>>> Archaeologist.Biography 
    572 &lt;unbound method Archaeologist.synapses> 
     593&lt;unbound method Archaeologist.related_units> 
    573594>>> Eversley = Archaeologist(Height=(6.417)) 
    574595>>> Eversley.Biography 
    575 &lt;bound method Archaeologist.synapses of &lt;__main__.Archaeologist 
     596&lt;bound method Archaeologist.related_units of &lt;__main__.Archaeologist 
    576597object at 0x011A1930>> 
    577598>>> bios = Eversley.Biography() 
     
    584605which is why we get an empty iterator at this point. At the other extreme 
    585606(when you have hundreds of Biographies to filter), you can pass an optional 
    586 <tt>Expression</tt> object to the synapse method. When you do, the list of 
    587 associated Units will be filtered accordingly.</p> 
    588  
    589 <p>Because the synapse method names are formed automatically, you need 
     607<tt>Expression</tt> object to the related_units method. When you do, the 
     608list of associated Units will be filtered accordingly.</p> 
     609 
     610<p>Because the related_units method names are formed automatically, you need 
    590611to take care not to use the names of Unit classes for your Unit properties. 
    591612In our example, we used "ArchID" for the name of our "foreign key". 
    592613If we had used "Archaeologist" instead, we would have had problems; 
    593614when we associated the classes, the <i>property</i> named "Archaeologist" 
    594 would have collided with the <i>synapse method</i> named "Archaeologist". 
    595 Be careful when naming your properties, and plan for the future.</p> 
    596  
    597 <p>Unlike some other ORM's, Dejavu doesn't cache far Units within 
    598 the near Unit. Each time you call the synapse method, the data is recalled 
     615would have collided with the <i>related_units method</i> named 
     616"Archaeologist". Be careful when naming your properties, and plan for the 
     617future.</p> 
     618 
     619<p>Unlike some other ORM's, Dejavu doesn't cache far Units within the near 
     620Unit. Each time you call the related_units method, the data is recalled 
    599621from your Sandbox. It is quite probable that those far Units are still 
    600622sitting in memory in the Sandbox, but they're not going to persist in 
    601623the near Unit itself in any way.</p> 
    602624 
    603 <p>Finally, some of you may want to override the default synapse methods. 
    604 Feel free; <tt>Arena.associate</tt> takes two optional arguments, which 
    605 should be callables that return the new function(s). See the source cod
    606 of <tt>Arena</tt> and the private method <tt>dejavu._synapses_func</tt> 
     625<p>Finally, some of you may want to override the default related_units 
     626methods. Feel free; <tt>associate</tt> takes two optional arguments, which 
     627should be callables that create and return the new method(s). See the sourc
     628code of <tt>dejavu</tt> and the method <tt>dejavu.relation_factory</tt> 
    607629for more information.</p> 
    608630 
     631<h4><tt>Unit.first()</tt></h4> 
     632<p>Associations also enable the <tt>first</tt> method of Units. It's an 
     633easy way to get a single related unit. Call it with a far Class and, 
     634optionally, keyword arguments. The method will look up the related 
     635properties and call sandbox.unit() for you, returning either the first 
     636such far Unit or None if not found.</p> 
    609637 
    610638<h3>Unit Engines</h3> 
     
    752780a <tt>SetID</tt>, and an <tt>Operand</tt>. Here's an example ruleset:</p> 
    753781<table> 
    754 <tr><th>Operation</th><th>SetID</th><th>Operand</th></tr> 
    755 <tr><td>CREATE</td><td>1</td><td>Invoice</td></tr> 
    756 <tr><td>FILTER</td><td>1</td><td>(Expression)</td></tr> 
    757 <tr><td>CREATE</td><td>2</td><td>Inventory</td></tr> 
    758 <tr><td>FILTER</td><td>2</td><td>(Expression)</td></tr> 
    759 <tr><td>TRANSFORM</td><td>2</td><td>Invoice</td></tr> 
    760 <tr><td>DIFFERENCE</td><td>1</td><td>2</td></tr> 
    761 <tr><td>RETURN</td><td>1</td><td></td></tr> 
     782<tr><th>Sequence</th><th>Operation</th><th>SetID</th><th>Operand</th></tr> 
     783<tr><td>1</td><td>CREATE</td><td>1</td><td>Invoice</td></tr> 
     784<tr><td>2</td><td>FILTER</td><td>1</td><td>(Expression)</td></tr> 
     785<tr><td>3</td><td>CREATE</td><td>2</td><td>Inventory</td></tr> 
     786<tr><td>4</td><td>FILTER</td><td>2</td><td>(Expression)</td></tr> 
     787<tr><td>5</td><td>TRANSFORM</td><td>2</td><td>Invoice</td></tr> 
     788<tr><td>6</td><td>DIFFERENCE</td><td>1</td><td>2</td></tr> 
     789<tr><td>7</td><td>RETURN</td><td>1</td><td></td></tr> 
    762790</table> 
    763791 
     
    766794of Units. In most cases, you will then FILTER that set. If you simply 
    767795created a set and then returned it, it would contain all Units of the 
    768 declared Type. When you filter a set, howevr, you remove Units from 
     796declared Type. When you filter a set, however, you remove Units from 
    769797the whole which do not match the filter's Expression.</p> 
    770798 
     
    881909<tt>roster</tt>. A roster is like a three-way map between Unit classes, 
    882910their names, and their assigned StorageManagers. You shouldn't manipulate 
    883 this structure on your own; instead, use the <tt>register</tt> method to 
    884 register each Unit class.</p> 
     911this structure on your own; instead, use the <tt>register</tt> or 
     912<tt>register_all</tt> methods to register each Unit class.</p> 
    885913 
    886914<p>The <tt>Arena</tt> object also manages the associations between Unit 
    887915classes in its <tt>associations</tt> attribute, which is a simple, 
    888 unweighted, undirected graph. In general, you should call 
    889 <tt>associate(cls, key, farClass, farKey)</tt> to add classes to this 
    890 graph. The only other common operation is to call 
    891 <tt>.associations.shortest_path(start, end)</tt>, to retrieve the 
    892 chain of associations between two Unit classes.</p> 
     916unweighted, undirected graph. Whenever you register a class, the Arena 
     917will add its associations to this graph. The only other common operation 
     918is to call <tt>.associations.shortest_path(start, end)</tt>, to retrieve 
     919the chain of associations between two Unit classes.</p> 
    893920 
    894921<hr /> 
  • trunk/doc/storage.html

    r37 r38  
    139139</ul> 
    140140 
     141<h5>Shelve</h5> 
     142<p>Persists Units to shelve-type db files. Extremely simple implementation; 
     143everything is pickled. Querying will be slow--every Unit is sucked in 
     144one-by-one and tested in pure Python using <tt>Expression.evaluate()</tt>. 
     145But for many applications, you don't need heavyweight query tools; 
     146for example, an online forum may only need topic content looked up by ID. 
     147Or small system tables that only get read at startup might benefit. 
     148Configuration entries:</p> 
     149<ul> 
     150    <li><b>Class:</b> <tt>dejavu.storage.storeshelve.StorageManagerShelve</tt></li> 
     151    <li><b>Path:</b> The file path (directory) in which to place db files. 
     152        Each Unit subclass will get its own file, of the same name as the 
     153        subclass.</li> 
     154</ul> 
     155 
    141156 
    142157<h4>Middleware</h4> 
     
    144159<p>Some Storage Managers act as "middleware", and can be chained together 
    145160to provide layered functionality. Consider, for example, the 
    146 <tt>CachingProxy</tt> class; it requires another Storage Manager 
     161<tt>CachingProxy</tt> class; it usually has another Storage Manager 
    147162"behind it", which it proxies. It can be used to cache objects between 
    148163client connections independently from the underlying, database-specific 
     
    153168 
    154169<h5>Caching Proxy</h5> 
    155 <p>Use this class to persist Units between client connections. It needs 
    156 another Storage Manager to proxy. Configuration entries:</p> 
     170<p>Use this class to persist Units in memory between client connections. 
     171It usually proxies another Storage Manager. Configuration entries:</p> 
    157172<ul> 
    158173    <li><b>Class:</b> <tt>dejavu.storage.CachingProxy</tt></li> 
    159     <li><b>Next Store:</b> Required. The name of the next Storage Manager 
    160         in the chain.</li> 
     174    <li><b>Next Store:</b> Optional. The name of the next Storage Manager 
     175        in the chain. If you do not specify a Next Store, Units will 
     176        only persist for the lifetime of the arena.</li> 
    161177    <li><b>Lifetime:</b> Optional. The recurrence string which declares 
    162178        how often to sweep Units out of the in-memory cache.</li> 
     
    164180 
    165181<h5>Burned Proxy</h5> 
    166 <p>Use this class to persist Units between client connections. It needs 
    167 another Storage Manager to proxy. Unlike the Caching Proxy above, this 
    168 Storage Manager recalls all Units at once upon the first request, and won't 
    169 recall them again from storage. They are "burned" into memory for the 
    170 lifetime of the application. Configuration entries:</p> 
     182<p>Use this class to persist Units in memory between client connections. 
     183It needs another Storage Manager to proxy. Unlike the Caching Proxy above, 
     184this Storage Manager recalls all Units at once upon the first request, 
     185and won't recall them again from storage. They are "burned" into memory 
     186for the lifetime of the application. Configuration entries:</p> 
    171187<ul> 
    172188    <li><b>Class:</b> <tt>dejavu.storage.BurnedProxy</tt></li> 
  • trunk/engines.py

    r37 r38  
    1414    import pickle 
    1515import dejavu 
    16 from dejavu import logic 
     16from dejavu import logic, associate 
    1717import xray 
    1818import sets 
     
    290290    def rules(self): 
    291291        """An ordered list of Rules for this Engine.""" 
    292         f = logic.filter(EngineID=self.ID) 
    293         allrules = [x for x in self.sandbox.recall(UnitEngineRule, f)] 
     292        allrules = [x for x in self.UnitEngineRule()] 
    294293        allrules.sort(dejavu.sort(u'Sequence')) 
    295294        return allrules 
     
    378377        else: 
    379378            return self.Owner in ('System', 'Public', user) 
     379 
     380associate(UnitEngine, 'ID', UnitEngineRule, 'EngineID') 
     381associate(UnitEngine, 'ID', UnitCollection, 'EngineID') 
    380382 
    381383 
     
    597599            B.release() 
    598600 
     601 
     602def register_classes(arena): 
     603    arena.register(UnitCollection) 
     604    arena.register(UnitEngine) 
     605    arena.register(UnitEngineRule) 
     606 
  • trunk/storage/__init__.py

    r37 r38  
    175175            if self.nextstore: 
    176176                self.nextstore.destroy(unit) 
    177             if unit.ID in cache
     177            try
    178178                del cache[unit.ID] 
     179            except KeyError: 
     180                pass 
     181            try: 
    179182                del self._recallTimes[unit.ID] 
     183            except KeyError: 
     184                pass 
    180185        finally: 
    181186            lock.release() 
     
    189194            if self.nextstore: 
    190195                self.nextstore.reserve(unit) 
     196            else: 
     197                if unit.ID is None: 
     198                    unit.ID = unit.sequencer.next(cache.keys()) 
    191199            # Pickle the Unit to discard extraneous attributes, 
    192200            # and avoid identity issues. 
  • trunk/storage/test_storeado.py

    r37 r38  
    4848        f = logic.Expression(lambda x: x.Legs == 4) 
    4949        sql = testSM.multiselect(zoo.Animal, f, [(zoo.Zoo, None)])[0] 
    50         expected = ("SELECT [djvAnimal].[Options], [djvAnimal].[Legs], " 
    51                     "[djvAnimal].[ID], [djvAnimal].[ZooID], " 
    52                     "[djvAnimal].[Name], [djvZoo].[Founded], " 
    53                     "[djvZoo].[LastEscape], [djvZoo].[ID], " 
    54                     "[djvZoo].[Opens], [djvZoo].[Name] FROM [djvAnimal] " 
    55                     "LEFT JOIN [djvZoo] ON [djvAnimal].[ZooID] = " 
    56                     "[djvZoo].[ID] WHERE [djvAnimal].[Legs] = 4") 
     50        expected = ("SELECT [djvAnimal].[Legs], [djvAnimal].[Name], " 
     51                    "[djvAnimal].[ZooID], [djvAnimal].[ID], " 
     52                    "[djvAnimal].[LastEscape], [djvAnimal].[Options], " 
     53                    "[djvZoo].[Founded], [djvZoo].[LastEscape], " 
     54                    "[djvZoo].[ID], [djvZoo].[Opens], [djvZoo].[Name] " 
     55                    "FROM [djvAnimal] LEFT JOIN [djvZoo] ON " 
     56                    "[djvAnimal].[ZooID] = [djvZoo].[ID] WHERE " 
     57                    "[djvAnimal].[Legs] = 4") 
    5758        self.assertEqual(sql, expected) 
    5859     
  • trunk/test_dejavu.py

    r37 r38  
    11import unittest 
    22import datetime 
     3 
    34import dejavu 
     5from dejavu import zoo, storage 
     6zoo.arena.add_store("default", storage.CachingProxy("default", zoo.arena)) 
    47 
    58 
     
    710     
    811    def test_Properties(self): 
    9         a = dejavu.Unit() 
     12        # Instance creation and population 
     13        f = datetime.date(1916, 10, 2) 
     14        z = zoo.Zoo(Name='San Diego Zoo', Founded=f) 
     15        self.assertEqual(z.dirty(), True) 
     16        self.assertEqual(zoo.Zoo.ID.type, int) 
     17        self.assertEqual(z.ID, None) 
     18        self.assertEqual(z.Name, 'San Diego Zoo') 
     19        self.assertEqual(type(z.Name), unicode) 
     20        self.assertEqual(z.Founded, f) 
     21        self.assertEqual(z.__class__.ID.index, True) 
    1022         
     23        a = zoo.Animal(Name='Giraffe', Legs=4) 
     24        self.assertEqual(a.dirty(), True) 
    1125        self.assertEqual(a.ID, None) 
    12         self.assertEqual(a.__class__.ID.type, int) 
    13         a.ID = 321 
    14         self.assertEqual(a.ID, 321) 
    15         self.assertEqual(a.dirty(), True) 
    16         a.ID = '444' 
    17         self.assertEqual(a.ID, 444) 
    18         # Should remapping the property attempt to coerce all instances? 
    19         a.set_property('ID', unicode) 
    20         a.ID = 'my thing' 
    21         self.assertEqual(a.ID, 'my thing') 
     26        self.assertEqual(a.Name, 'Giraffe') 
     27        self.assertEqual(a.Legs, 4) 
     28        self.assertEqual(a.__class__.ZooID.index, True) 
    2229         
    23         class Triggered(dejavu.UnitProperty): 
    24             def pre(self, unit, value): 
    25                 unit.preval = value 
    26              
    27             def post(self, unit, value): 
    28                 unit.postval = value 
     30        # Sandboxing 
     31        s = zoo.arena.new_sandbox() 
     32        s.memorize(z) 
     33        self.assertEqual(z.ID, 1) 
     34        s.memorize(a) 
     35        self.assertEqual(a.ID, 1) 
     36        z.add(a) 
     37        self.assertEqual(a.ZooID, 1) 
    2938         
    30         class Thing(dejavu.Unit): 
    31             Integer = dejavu.UnitProperty(int, index=True) 
    32             String = dejavu.UnitProperty(str, hints={'Size': 0}) 
    33             Unicode = dejavu.UnitProperty(unicode) 
    34             Datetime = Triggered(datetime.datetime) 
    35             Dict = dejavu.UnitProperty(dict) 
    36          
    37         # Instance creation and population 
    38         t = Thing(Integer=3, String='abc', Unicode=u'foo') 
    39         self.assertEqual(t.dirty(), False) 
    40         self.assertEqual(t.Integer, 3) 
    41         self.assertEqual(t.String, 'abc') 
    42         self.assertEqual(type(t.String), str) 
    43         self.assertEqual(t.Unicode, u'foo') 
    44         self.assertEqual(type(t.Unicode), unicode) 
    45         self.assertEqual(t.Datetime, None) 
    46         self.assertEqual(t.Dict, None) 
    47          
    48         # Index attribute 
    49         self.assertEqual(t.__class__.Integer.index, True) 
    50          
    51         # Triggers. Unit needs a sandbox or pre/post won't be called. 
    52         t.sandbox = True 
    53         self.assertEqual(hasattr(t, 'preval'), False) 
    54         t.Datetime = aDate = datetime.datetime(2004, 10, 20) 
    55         self.assertEqual(t.preval, aDate) 
    56         self.assertEqual(t.postval, aDate) 
    57         self.assertEqual(t.dirty(), True) 
    58          
    59         t.cleanse() 
    60         t.Dict = aDict = {'k': 'blah'} 
    61         self.assertEqual(t.dirty(), True) 
    62         self.assertEqual(t.Dict, aDict) 
     39        # Triggers 
     40        z.cleanse() 
     41        self.assertEqual(z.dirty(), False) 
     42        a.LastEscape = d = datetime.datetime(2004, 10, 20) 
     43        self.assertEqual(a.LastEscape, d) 
     44        self.assertEqual(z.LastEscape, d) 
     45        self.assertEqual(z.dirty(), True) 
     46 
    6347 
    6448if __name__ == "__main__": 
  • trunk/zoo.py

    r37 r38  
    11import datetime 
     2import dejavu 
     3from dejavu import Unit, UnitProperty, associate 
    24 
    3 import dejavu 
    4 from dejavu import Unit, UnitProperty 
    5  
    6 arena = dejavu.Arena() 
    75 
    86class Zoo(Unit): 
     
    1311 
    1412 
     13class EscapeProperty(UnitProperty): 
     14    def post(self, unit, value): 
     15        z = unit.first(Zoo) 
     16        if z: 
     17            z.LastEscape = unit.LastEscape 
     18 
    1519class Animal(Unit): 
    1620    Name = UnitProperty() 
    17     ZooID = UnitProperty(int
     21    ZooID = UnitProperty(int, index=True
    1822    Legs = UnitProperty(int) 
    1923    Options = UnitProperty(dict) 
     24    LastEscape = EscapeProperty(datetime.datetime) 
    2025 
    21 arena.associate(Zoo, 'ID', Animal, 'ZooID') 
     26associate(Zoo, 'ID', Animal, 'ZooID') 
     27 
    2228 
    2329class Exhibit(Unit): 
     
    2531    Animals = UnitProperty(list) 
    2632 
    27 arena.associate(Zoo, 'ID', Exhibit, 'ZooID') 
     33associate(Zoo, 'ID', Exhibit, 'ZooID') 
     34 
     35arena = dejavu.Arena() 
     36arena.register_all(globals())