Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

Changeset 33

Show
Ignore:
Timestamp:
11/21/04 03:30:23
Author:
fumanchu
Message:

Rewrote handling of Unit.dirty. For UnitProperty? types which were mutable (like list and dict), we had to explicitly set dirty, because dirty only set automatically on rebind, not mutate. New system uses a hash of unit._properties; unit.dirty() compares unit._initial_property_hash with the current hash, returning True if they differ. Unit.cleanse() sets _initial_property_hash to the current hash, and is called in Unit.init().

Files:

Legend:

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

    r32 r33  
    11import ConfigParser 
    22import datetime 
    3  
     3import sha 
     4try: 
     5    import cPickle as pickle 
     6except ImportError: 
     7    import pickle 
    48 
    59from dejavu.containers import * 
     
    134138            if unit.sandbox and self.pre: 
    135139                self.pre(unit, value) 
    136             unit.dirty = True 
    137140            unit._properties[self.key] = value 
    138141            if unit.sandbox and self.post: 
     
    201204        these processes should set the sandbox attribute. 
    202205     
    203     dirty: a flag indicating whether elements in the _properties dictionary 
     206    dirty: indicates whether elements in the _properties dictionary 
    204207        have been modified. This flag is used by Sandboxes to optimize 
    205208        forget(): they do not ask Storage Managers to save data for Units 
     
    228231         
    229232        self.sandbox = None 
    230         self.dirty = False 
    231233         
    232234        for k, v in kwargs.iteritems(): 
    233235            setattr(self, k, v) 
     236        self.cleanse() 
     237     
     238    def _property_hash(self): 
     239        return sha.new(pickle.dumps(self._properties)).digest() 
     240     
     241    def dirty(self): 
     242        return self._initial_property_hash != self._property_hash() 
     243     
     244    def cleanse(self): 
     245        self._initial_property_hash = self._property_hash() 
    234246     
    235247    def set_property(cls, key, type=unicode, index=False, 
     
    294306     
    295307    def __getstate__(self): 
    296         return (self._properties, self.dirty
     308        return (self._properties, self._initial_property_hash
    297309     
    298310    def __setstate__(self, state): 
    299311        self.sandbox = None 
    300         self._properties, self.dirty = state 
     312        self._properties, self._initial_property_hash = state 
    301313     
    302314    def add(self, unit): 
     
    517529         
    518530        # Ask the store to accept the unit, assigning it an ID if 
    519         # necessary. The store should also set unit.dirty to False 
    520         # if it saves the whole unit state on this call or not
     531        # necessary. The store should also call unit.cleanse() 
     532        # if it saves the whole unit state on this call
    521533        store = self.arena.storage(cls) 
    522534        if store: 
     
    738750                unit.on_repress() 
    739751             
    740             if store and unit.dirty
     752            if store and unit.dirty()
    741753                store.save(unit) 
    742754     
     
    753765        cls = unit.__class__ 
    754766        store = self.arena.storage(cls) 
    755         if store and unit.dirty
     767        if store and unit.dirty()
    756768            store.save(unit) 
    757769         
  • trunk/doc/framework.html

    r32 r33  
    150150        which is a working decompiler for ADO databases. You should be able 
    151151        to tweak it for most databases with a couple of SQL syntax changes. 
    152         [It does the decompilation in one pass right now, which is fragile 
    153         but works. I hope to move it to a two-pass version soon.]</li> 
     152        </li> 
    154153    <li><tt>ADOSQLDecompiler</tt> is built on a simple Visitor-style base 
    155         class, <tt>codewalk.LambdaDecompiler</tt>. More complicated extensions 
    156         are easily added to this base class; each bytecode in the Expressio
    157         (Python lambda) gets its own method call.</li> 
     154        class, <tt>codewalk.LambdaDecompiler</tt>. More complicated 
     155        extensions are easily added to this base class; each bytecode i
     156        the Expression (Python lambda) gets its own method call.</li> 
    158157    <li>You don't have to handle globals or cell references within the 
    159158        lambda--when the lambda gets wrapped in an Expression, all free 
     
    189188        in the sequence. You should probably lock this whole method in a 
    190189        <tt>threading.Lock</tt>.</li> 
    191     <li><b>save</b>: If <tt>not unit.dirty</tt>, you can exit. Otherwise, 
     190    <li><b>save</b>: If <tt>not unit.dirty()</tt>, you can exit. Otherwise, 
    192191        iterate through the Unit's properties() and persist each value, 
    193192        using an Adapter to coerce each value to the type expected by your 
    194193        database (or not, it's up to you; storeado special-cases 
    195         UnitCollection, for example). Set <tt>unit.dirty = False</tt>.</li> 
     194        UnitCollection, for example). If you are able to persist all values, 
     195        call <tt>unit.cleanse()</tt> to mark the Unit as no longer dirty. 
     196        </li> 
    196197    <li><b>destroy</b>: <tt>"DELETE * FROM [%(table)s] WHERE ID =  
    197198        %(id)s"</tt>. That's all.</li> 
  • trunk/engines.py

    r32 r33  
    5656     
    5757    def __getstate__(self): 
    58         return (self._properties, self.dirty
     58        return (self._properties, self._initial_property_hash
    5959     
    6060    def __setstate__(self, state): 
    6161        self.sandbox = None 
    6262        self._mutex = threading.RLock() 
    63         self._properties, self.dirty = state 
     63        self._properties, self._initial_property_hash = state 
    6464     
    6565    def acquire(self): 
  • trunk/readme.py

    r32 r33  
    4949 
    5050todo = """ 
    51 1. Batched triggers: 
    52     Some pre/post triggers should be delayed until end of request, 
    53     particularly those which affect related Units. Also on_forget(), etc. 
    54     There should be a generic trigger mechanism with the ability for 
    55     client code to register new trigger points. Probably each Unit class 
    56     should maintain an attribute triggers = {}, where each key is a string 
    57     and each value is a callable which gets passed "self". Standard 
    58     triggers would include: 
    59         1. Pre-modify property X 
    60         2. Post-modify property X. 
    61         3. Pre-forget/post-forget. 
    62         4. on_flush (end of request) 
    63 2. For UnitProperty types which are mutable (like dicts), we currently have to 
    64     explicitly set dirty, because dirty only sets automatically on rebind, 
    65     not mutate. Fix if possible, or doc the heck out of it. 
    66 3. Speed issues: 
    67     1. Chained, related Unit classes would be nice to have in Expressions. 
    68         lambda x: x.Amount > 0 and x.Characteristic.Type = 'grr' 
    69     2. It would be nice to declare e.g. Job has only one Project; 
    70         this would allow job.Project() to set quota = 1 on recall. 
    71     3. Batch memorize when adding several units with auto ID's. 
    72 4. u"SELECT * FROM [djvMissionTrip] WHERE ((([djvMissionTrip].[LastDate] <> Null) 
    73     and ([djvMissionTrip].[LastDate] >= #10/14/2004#)) 
    74     and ([djvMissionTrip].[Field] = 'BC')) 
    75     and ([u'C'] Like '%djvMissionTrip].[Plan%')" 
    76 5. More SM's. 
    77     SQLite 
    78     PostgreSQL 
    79     shelve 
    80     dbm 
    81  
    82  
    83512.0: 
    84     Classes are associated across Arenas. :/ But that shouldn't be a 
     52    1. Batched triggers: 
     53        Some pre/post triggers should be delayed until end of request, 
     54        particularly those which affect related Units. Also on_forget(), 
     55        etc. There should be a generic trigger mechanism with the ability 
     56        for client code to register new trigger points. Probably each Unit 
     57        class should maintain an attribute triggers = {}, where each key 
     58        is a string and each value is a callable which gets passed "self". 
     59        Standard triggers points would include: 
     60            1. Pre-modify property X 
     61            2. Post-modify property X. 
     62            3. Pre-forget/post-forget. 
     63            4. on_flush (end of request) 
     64    2. Speed issues: 
     65        1. Chained, related Unit classes would be nice to have in Expressions. 
     66            lambda x: x.Amount > 0 and x.Characteristic.Type = 'grr' 
     67        2. It would be nice to declare e.g. Job has only one Project; 
     68            this would allow job.Project() to set quota = 1 on recall. 
     69        3. Batch memorize when adding several units with auto ID's. 
     70    3. More SM's. 
     71        SQLite 
     72        PostgreSQL 
     73        MySQL 
     74        shelve 
     75        dbm 
     76    4. Classes are associated across Arenas. :/ But that shouldn't be a 
    8577        huge issue--it only affects those who wish to deploy the same app 
    8678        twice within the same process--an odd corner case. 
    87     Add the new (python 2.3) Decimal type. 
    88     UnitProperty.pre should be able to modify value. 
    89     http://sqlobject.org/docs/FAQ.html would be a great page to rewrite 
     79    5. Add the new (python 2.3) Decimal type. 
     80    6. UnitProperty.pre should be able to modify value. 
     81    7. http://sqlobject.org/docs/FAQ.html would be a great page to rewrite 
    9082        with respect to dejavu instead of sqlobject. 
    9183""" 
     
    9890    2. UnitProperty now supplies .key from local attribute name when 
    9991        specified in class body (see MetaUnit __init__). 
     92    3. Made Decompiler less fragile by adding stack sentinels: 
     93        cannot_represent, table_arg, kw_arg 
     94    4. Rewrote handling of Unit.dirty. For UnitProperty types which were 
     95        mutable (like list and dict), we had to explicitly set dirty, 
     96        because dirty only set automatically on rebind, not mutate. 
     97        New system uses a hash of unit._properties; unit.dirty() compares 
     98        unit._initial_property_hash with the current hash, returning True 
     99        if they differ. Unit.cleanse() sets _initial_property_hash to the 
     100        current hash, and is called in Unit.__init__(). 
    100101 
    1011021.2.5 (11/15/04): 
  • trunk/storage/__init__.py

    r32 r33  
    159159        """Store the unit.""" 
    160160        # Defer store.save() for sweepers. 
    161         if unit.dirty
     161        if unit.dirty()
    162162            lock = self._get_lock(unit.__class__) 
    163163            try: 
     
    205205                    or lastRecall < lastSweepTime): 
    206206                    unit = pickle.loads(cache[id]) 
    207                     if unit.dirty
     207                    if unit.dirty()
    208208                        self.nextstore.save(unit) 
    209209                     
  • trunk/storage/storeado.py

    r32 r33  
    855855        The ID should be supplied by UnitSequencers via reserve(). 
    856856        """ 
    857         if unit.dirty or forceSave: 
     857        if unit.dirty() or forceSave: 
    858858            cls = unit.__class__ 
    859859            clsname = cls.__name__ 
     
    884884            anRS.Update() 
    885885            anRS.Close() 
    886             unit.dirty = False 
     886            unit.cleanse() 
    887887     
    888888    def save_expanded(self, unit, key): 
  • trunk/storage/storeodbc.py

    r32 r33  
    278278            coercer.consume(eachKey, row[self.colIndices[eachKey.lower()]]) 
    279279        unit.concrete = True 
    280         unit.dirty = False 
     280        unit.cleanse() 
    281281        return True 
    282282     
     
    423423        The ID should be already supplied by the UnitServer(s). 
    424424        """ 
    425         if unit.dirty or forceSave: 
     425        if unit.dirty() or forceSave: 
    426426            # Use an UPDATE command. 
    427427            SETAtoms = [u"%s = %s" % (eachKey, savecoercer.coerce(getattr(unit, eachKey))) 
     
    449449                                   % (tablename, unit.ID)) 
    450450                self.execute(insertStatement) 
    451             unit.dirty = False 
     451            unit.cleanse() 
    452452        return True 
    453453     
  • trunk/storage/test_storeado.py

    r32 r33  
    191191        # This broke on 5/10/04, because "== None" wasn't succeeding as "= Null". 
    192192        trial(lambda x: x.DateTo == None, u"[djvThings].[DateTo] Is Null", False) 
    193      
     193         
     194        # Another one that broke sometime in 2004. Rev 32 seems to have fixed it. 
     195        trial(lambda x: 'C' in x.Plan, "[djvThings].[Plan] Like '%C%'", True) 
     196         
    194197        # Multiple arguments (? Why should this be supported?) 
    195198        trial(lambda x, y, z: x.Date == 3 and y.Qty > 4 and z.Qty < 20, 
  • trunk/test_dejavu.py

    r32 r33  
    1313        a.ID = 321 
    1414        self.assertEqual(a.ID, 321) 
    15         self.assertEqual(a.dirty, True) 
     15        self.assertEqual(a.dirty(), True) 
    1616        a.ID = '444' 
    1717        self.assertEqual(a.ID, 444) 
     
    3333            Unicode = dejavu.UnitProperty(unicode) 
    3434            Datetime = Triggered(datetime.datetime) 
     35            Dict = dejavu.UnitProperty(dict) 
    3536         
    3637        # Instance creation and population 
    3738        t = Thing(Integer=3, String='abc', Unicode=u'foo') 
     39        self.assertEqual(t.dirty(), False) 
    3840        self.assertEqual(t.Integer, 3) 
    3941        self.assertEqual(t.String, 'abc') 
     
    4244        self.assertEqual(type(t.Unicode), unicode) 
    4345        self.assertEqual(t.Datetime, None) 
     46        self.assertEqual(t.Dict, None) 
    4447         
    4548        # Index attribute 
    4649        self.assertEqual(t.__class__.Integer.index, True) 
    4750         
    48         # Triggers. Unit needs a sandbox or pre, post won't be called. 
     51        # Triggers. Unit needs a sandbox or pre/post won't be called. 
    4952        t.sandbox = True 
    5053        self.assertEqual(hasattr(t, 'preval'), False) 
     
    5255        self.assertEqual(t.preval, aDate) 
    5356        self.assertEqual(t.postval, aDate) 
    54  
     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) 
    5563 
    5664if __name__ == "__main__":