Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

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

Changeset 113

Show
Ignore:
Timestamp:
11/29/05 04:49:56
Author:
fumanchu
Message:

Fix for #29 (arbitrary primary keys):

  1. New Unit.identifiers, a tuple of its UnitProperty? attributes which make Units of that class unique.
  2. New Unit.identity(). Returns a tuple of values matching the keys specified in Unit.identifiers.
  3. Arena and other caches now use Unit.identity() as their lookup keys.
  4. engines.UnitCollection?, UnitEngineRule? now use Unit.identity() for their keys. This means existing UnitCollection? objects (i.e., those persisted in storage) will have to be migrated to the new format.
  5. UnitSequencer? API has changed; replace next(ids) with assign(unit, ids).
  6. You can now remove inherited UnitProperties? from a subclass by assigning to None. For example, to remove the default ID property, set "ID = None" inside the definition of the subclass (the metaclass does the erasing for you).
  7. New engine tests in zoo_fixture.py.
Files:

Legend:

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

    r112 r113  
    198198        unit.sandbox = self 
    199199         
    200         # Ask the store to accept the unit, assigning it an ID if 
    201         # necessary. The store should also call unit.cleanse() 
    202         # if it saves the whole unit state on this call. 
     200        # Ask the store to accept the unit, assigning it primary key values 
     201        # if necessary. The store should also call unit.cleanse() if it 
     202        # saves the whole unit state on this call. 
    203203        self.arena.storage(cls).reserve(unit) 
    204204         
    205205        # Insert the unit into the cache. 
    206         self._cache(cls)[unit.ID] = unit 
    207         self.arena.log("MEMORIZE %s: %s" % (cls.__name__, unit.ID), LOGMEMORIZE) 
     206        id = unit.identity() 
     207        self._cache(cls)[id] = unit 
     208        self.arena.log("MEMORIZE %s: %s" % (cls.__name__, id), LOGMEMORIZE) 
    208209         
    209210        # Do this at the end of the func, since most on_memorize 
    210         # will want to have an ID when called. 
     211        # will want to have an identity when called. 
    211212        if hasattr(unit, "on_memorize"): 
    212213            unit.on_memorize() 
     
    216217        cls = unit.__class__ 
    217218         
    218         self.arena.log("FORGET %s: %s" % (cls.__name__, unit.ID), LOGFORGET) 
     219        id = unit.identity() 
     220        self.arena.log("FORGET %s: %s" % (cls.__name__, id), LOGFORGET) 
    219221        self.arena.storage(cls).destroy(unit) 
    220222         
    221         del self._cache(cls)[unit.ID
     223        del self._cache(cls)[id
    222224         
    223225        if hasattr(unit, "on_forget"): 
     
    240242                fc.co_names[-1] == 'ID'): 
    241243                ID = fc.co_consts[-1] 
    242                 unit = cache.get(ID
     244                unit = cache.get((ID,)
    243245                if unit is not None: 
    244246                    # Do NOT call on_recall here. That should be called 
     
    249251        # Query Cache and Storage. 
    250252        for unit in self.arena.storage(cls).recall(cls, expr): 
    251             ID = unit.ID 
     253            id = unit.identity() 
    252254            # Very important that we check for existing unit, as its 
    253255            # state may have changed in memory but not in storage. 
    254256            # Make sure the cache lookup and get happens atomically. 
    255             existing = cache.get(ID
     257            existing = cache.get(id
    256258            if existing: 
    257259                yield existing 
     
    259261                unit.sandbox = self 
    260262                confirmed = True 
    261                 cache[ID] = unit 
     263                cache[id] = unit 
    262264                if hasattr(unit, 'on_recall'): 
    263265                    try: 
     
    309311            for index in xrange(len(unitset)): 
    310312                unit = unitset[index] 
    311                 ID = unit.ID 
     313                id = unit.identity() 
    312314                cache = self._cache(unit.__class__) 
    313                 if ID in cache: 
     315                if id in cache: 
    314316                    # Keep the unit which is in our cache! 
    315                     unitset[index] = cache[ID
     317                    unitset[index] = cache[id
    316318                else: 
    317                     cache[ID] = unit 
     319                    cache[id] = unit 
    318320                    unit.sandbox = self 
    319321                    if hasattr(unit, 'on_recall'): 
     
    354356                yield tuple([getattr(unit, attr) for attr in attrs]) 
    355357         
    356         # Add the ID attribute if not present. This is necessary to 
    357         # avoid duplicating objects which are already in our cache. 
     358        # Add the identity attribute(s) if not present. This is necessary 
     359        # to avoid duplicating objects which are already in our cache. 
    358360        fields = list(attrs) 
    359         if "ID" not in fields: 
    360             fields.append("ID") 
    361         index_of_id = fields.index("ID") 
     361        indices = [] 
     362        added_fields = 0 
     363        for prop in cls.identifiers: 
     364            if prop.key not in fields: 
     365                added_fields += 1 
     366                fields.append(prop.key) 
     367            indices.append(fields.index(prop.key)) 
    362368         
    363369        for row in self.arena.storage(cls).view(cls, fields, expr): 
    364             if row[index_of_id] not in cache: 
    365                 if "ID" not in attrs: 
    366                     # Remove the ID column from the tuple. 
    367                     row = row[:len(row) - 1] 
     370            id = tuple([row[x] for x in indices]) 
     371            if id not in cache: 
     372                if added_fields: 
     373                    # Remove the added identifier columns from the row. 
     374                    row = row[:len(row) - added_fields] 
    368375                yield row 
    369376     
     
    399406    def count(self, cls, expr): 
    400407        """count(cls, expr) -> Number of Units of class 'cls'.""" 
    401         return len(self.distinct(cls, ['ID'], expr)) 
     408        idnames = [prop.key for prop in cls.identifiers] 
     409        return len(self.distinct(cls, idnames, expr)) 
    402410     
    403411    #################################### 
     
    421429        """repress(unit). Remove unit from cache (but don't destroy).""" 
    422430        cls = unit.__class__ 
    423         self.arena.log("REPRESS %s: %s" % (cls.__name__, unit.ID), LOGREPRESS) 
     431        id = unit.identity() 
     432        self.arena.log("REPRESS %s: %s" % (cls.__name__, id), LOGREPRESS) 
    424433         
    425434        if hasattr(unit, "on_repress"): 
     
    429438        self.arena.storage(cls).save(unit) 
    430439         
    431         del self._cache(cls)[unit.ID
     440        del self._cache(cls)[id
    432441     
    433442    def flush_all(self): 
  • trunk/doc/advanced.html

    r112 r113  
    249249        to create one (unless this is a pass-through SM). Grab all existing 
    250250        distinct ID's (which you are storing), and pass them to 
    251         <tt>unit.sequencer.next(ids)</tt>, which should return the next ID 
    252         in the sequence. You should probably lock this whole method in a 
     251        <tt>unit.sequencer.assign(unit, ids)</tt>, which should assign the 
     252        next ID in the sequence to the Unit. Remember that when we say "ID" 
     253        we mean a tuple of identifier (UnitProperty attribute) values. 
     254        You should probably lock this whole method in a 
    253255        <tt>threading.Lock</tt>.</li> 
    254256    <li><b>save</b>: If <tt>not unit.dirty()</tt>, you can exit. Otherwise, 
     
    259261        call <tt>unit.cleanse()</tt> to mark the Unit as no longer dirty. 
    260262        </li> 
    261     <li><b>destroy</b>: <tt>"DELETE * FROM [%(table)s] WHERE ID =  
    262         %(id)s"</tt>. That's all.</li> 
     263    <li><b>destroy</b>: <tt>"DELETE FROM [%s] WHERE %s;" % 
     264        (self.tablename(unit), self.id_clause(unit))</tt>. That's all.</li> 
    263265    <li><b>recall</b>: If no Expression is supplied, return all Units. 
    264266        Otherwise, use a decompiler to produce SQL which you can then use 
  • trunk/doc/managing.html

    r112 r113  
    274274<h4>Collections: Lists of Units</h4> 
    275275<p>The <tt>UnitCollection</tt> class provides a means of storing a list 
    276 of Units, or rather, a list of Unit ID's. You use its <tt>Type</tt> 
     276of Units, or rather, a list of Unit identifiers. You use its <tt>Type</tt> 
    277277property to indicate the class of the indexed Units. That value should be 
    278278the <b>name</b> of the Unit Class, <b>not</b> the class object itself 
     
    295295which will 
    296296look up the Units and return them in a list. Since the Collection only 
    297 stores ID's, it is possible that one of the indexed Units may have been 
    298 destroyed since the list was built. The <tt>units</tt> method simply 
     297stores identifiers, it is possible that one of the indexed Units may have 
     298been destroyed since the list was built. The <tt>units</tt> method simply 
    299299passes over these "phantom" Units. You can inspect the full list of IDs 
    300300in the Collection (whether they reference existing Units or not) with 
     
    337337your users, "Engine #23569 is an 'Armadillo' engine," when it produces 
    338338Collections of <tt>Armadillo</tt> Units. The only time you might want to 
    339 set this value is when you first create the Engine, before you have added 
    340 any Rules.</p> 
     339set this value manually is when you first create the Engine, before you 
     340have added any Rules.</p> 
    341341 
    342342<h4><a name='unitenginerules'>Rules</a></h4> 
  • trunk/doc/modeling.html

    r112 r113  
    6262 
    6363<h4>Unit ID's</h4> 
    64 <p>The <tt>Unit</tt> base class possesses a single Unit Property, an int 
    65 named 'ID'. If you wish to use ID's of a different type, simply override 
    66 the ID attribute in your subclass: 
     64<p>All Units possess an <tt>identifiers</tt> attribute, a tuple of 
     65their UnitProperties which define the uniqueness of a Unit. The 
     66<tt>Unit</tt> base class possesses a single Unit Property, an int 
     67named 'ID', and its <tt>identifiers</tt> attribute is therefore 
     68<tt>(ID,)</tt>. That's not a string in the tuple; it's a reference 
     69to the actual UnitProperty class. If you wish to use identifiers 
     70of a different number, types, or names, simply replace the 
     71<tt>identifiers</tt> attribute in your subclass:</p> 
     72 
    6773<pre>class Printer(Unit): 
    68     ID = UnitProperty(unicode)</pre> 
    69 Every Unit must possess an ID property. This ensures that each Unit within 
    70 the system is unique.</p> 
     74    # Set ID to None to remove the ID property from this subclass. 
     75    ID = None 
     76     
     77    Model = UnitProperty(unicode) 
     78    UnitNumber = UnitProperty(int) 
     79    identifiers = (Model, UnitNumber) 
     80</pre> 
     81 
     82<p>Every Unit must possess at least one identifier. This ensures that 
     83each Unit within the system is unique. You should consider any 
     84UnitProperty which is one of the identifiers to be read-only 
     85after a Unit has been memorized.</p> 
    7186 
    7287<h4>Creating and Populating Properties</h4> 
     
    260275 
    261276<h4>Sequencing</h4> 
    262 <p>Every <tt>Unit</tt> has an <tt>ID</tt> property. The default ID property 
     277<p>Every <tt>Unit</tt> has one or more identifiers. The default ID property 
    263278is of type <tt>int</tt>; however, you can override that to whatever type 
    264 you like. As long as you provide your own IDs for Units, nothing will 
    265 break--you can memorize and recall Units without problems. However, if 
    266 you memorize a Unit with an ID of <tt>None</tt>, the Sandbox may attempt 
    267 to provide an ID for it.</p> 
     279you like. As long as you provide your own identifier values for Units, 
     280nothing will break--you can memorize and recall Units without problems. 
     281However, if you memorize a Unit with an ID of <tt>None</tt>, the Sandbox 
     282may attempt to provide an ID for it.</p> 
    268283 
    269284<p>The <tt>Unit</tt> base class possesses a <tt>sequencer</tt> attribute 
     
    429444 
    430445<p>The <tt>distinct</tt> function can also be used as a <tt>count</tt> 
    431 function by passing attrs = ['ID']. Sandboxes provide a 
    432 <tt class='def'>count(cls, expr)</tt> method which does just this.</p> 
     446function by passing <tt>attrs = [prop.key for prop in cls.identifiers]</tt>. 
     447Sandboxes provide a <tt class='def'>count(cls, expr)</tt> method which does 
     448just this.</p> 
    433449 
    434450 
  • trunk/engines.py

    r112 r113  
    1919 
    2020class UnitCollection(dejavu.Unit): 
    21     """A Set of Unit IDs. 
     21    """A Set of Unit identifiers. 
    2222     
    2323    Type: Unit Type of all Units referenced by this collection. 
     
    9191    def units(self, quota=None): 
    9292        cls = self.unit_class() 
     93        idnames = [prop.key for prop in cls.identifiers] 
     94         
    9395        output = [] 
    9496        self.acquire() 
     
    9799                if quota and i >= quota: 
    98100                    break 
    99                 unit = self.sandbox.unit(cls, ID=eachID
     101                unit = self.sandbox.unit(cls, **dict(zip(idnames, eachID))
    100102                if unit: 
    101103                    output.append(unit) 
     
    173175         
    174176        TRANSFORM: 
    175             If the Operation is 'TRANSFORM', the Operand shall be the name 
    176             of a Unit type. The snapshot will consist of IDs of all units 
    177             of that Type which are associated with the current snapshot. 
     177            If the Operation is 'TRANSFORM', the Operand shall be the name of 
     178            a Unit type. The snapshot will consist of the identifiers of all 
     179            units of that Type which are associated with the current snapshot. 
    178180         
    179181        FILTER: 
    180182            If the Operation is 'FILTER', the Operand shall be a 
    181             logic.Expression, and the snapshot will consist of the IDs of 
    182             Units which match the Expression. 
     183            logic.Expression, and the snapshot will consist of the 
     184            identifiers of Units which match the Expression. 
    183185         
    184186        So, a typical Engine might have a set of rules which look like: 
     
    452454                    mem = A.Members 
    453455                    for unit in self.sandbox.recall(cls): 
    454                         id = unit.ID 
     456                        id = unit.identity() 
    455457                        if id not in B.Members: 
    456458                            mem.append(id) 
     
    474476                A.universal = False 
    475477                for unit in self.sandbox.recall(cls, expr): 
    476                     id = unit.ID 
     478                    id = unit.identity() 
    477479                    if id not in mem: 
    478480                        mem.append(id) 
    479481            else: 
    480482                newset = [] 
     483                idnames = [prop.key for prop in cls.identifiers] 
    481484                for id in mem: 
    482                     unit = self.sandbox.unit(cls, ID=id
     485                    unit = self.sandbox.unit(cls, **dict(zip(idnames, id))
    483486                    if unit and expr(unit): 
    484487                        newset.append(id) 
     
    534537                mem = A.Members 
    535538                for unit in self.sandbox.recall(cls): 
    536                     mem.append(unit.ID
     539                    mem.append(unit.identity()
    537540            finally: 
    538541                A.release() 
     
    566569                                farUnits = [farUnits] 
    567570                        for farUnit in farUnits: 
    568                             farid = farUnit.ID 
     571                            farid = farUnit.identity() 
    569572                            if farid not in newset: 
    570573                                newset.append(farid) 
    571574                    A.universal = False 
    572575                else: 
     576                    idnames = [prop.key for prop in cls.identifiers] 
    573577                    for id in A.Members: 
    574                         unit = self.sandbox.unit(cls, ID=id
     578                        unit = self.sandbox.unit(cls, **dict(zip(idnames, id))
    575579                        if unit: 
    576580                            farUnits = ua.__get__(unit)() 
     
    581585                                    farUnits = [farUnits] 
    582586                            for farUnit in farUnits: 
    583                                 farid = farUnit.ID 
     587                                farid = farUnit.identity() 
    584588                                if farid not in newset: 
    585589                                    newset.append(farid) 
  • trunk/storage/__init__.py

    r112 r113  
    181181            if self.nextstore: 
    182182                for unit in self.nextstore.recall(unitClass, expr): 
    183                     if unit.ID not in cache: 
     183                    id = unit.identity() 
     184                    if id not in cache: 
    184185                        # Pickle the Unit to discard extraneous attributes, 
    185186                        # and avoid identity issues. 
    186                         cache[unit.ID] = pickle.dumps(unit) 
    187                         self._recallTimes[unit.ID] = currentTime 
     187                        cache[id] = pickle.dumps(unit) 
     188                        self._recallTimes[id] = currentTime 
    188189                         
    189190                        # Only add to matches if it wasn't already in our 
    190191                        # cache (because stored units may have stale data). 
    191                         if unit.ID not in matches: 
    192                             matches[unit.ID] = unit 
     192                        if id not in matches: 
     193                            matches[id] = unit 
    193194             
    194195            return iter(matches.values()) 
     
    208209        try: 
    209210            cache = self._caches[unit.__class__] 
    210             cache[unit.ID] = pickle.dumps(unit) 
     211            cache[unit.identity()] = pickle.dumps(unit) 
    211212        finally: 
    212213            lock.release() 
     
    217218        lock = self._get_lock(unitClass) 
    218219        try: 
     220            id = unit.identity() 
    219221            cache = self._caches[unitClass] 
    220222            if self.nextstore: 
    221223                self.nextstore.destroy(unit) 
    222224            try: 
    223                 del cache[unit.ID
     225                del cache[id
    224226            except KeyError: 
    225227                pass 
    226228            try: 
    227                 del self._recallTimes[unit.ID
     229                del self._recallTimes[id
    228230            except KeyError: 
    229231                pass 
     
    251253            if self.nextstore: 
    252254                 
    253                 # Add the ID attribute if not present. This is necessary to 
    254                 # avoid duplicating objects which are already in our cache. 
     255                # Add the identifier attributes if not present. This is 
     256                # necessary to avoid duplicating objects which are 
     257                # already in our cache. 
    255258                fields = list(attrs) 
    256                 if "ID" not in fields: 
    257                     fields.append("ID") 
    258                 index_of_id = fields.index("ID") 
     259                indices = [] 
     260                added_fields = 0 
     261                for prop in cls.identifiers: 
     262                    if prop.key not in fields: 
     263                        fields.append(prop.key) 
     264                        added_fields += 1 
     265                    indices.append(fields.index(prop.key)) 
    259266                 
    260267                for row in self.nextstore.view(cls, fields, expr): 
    261                     if row[index_of_id] not in cache: 
    262                         if "ID" not in attrs: 
    263                             # Remove the ID column from the tuple. 
    264                             row = row[:len(row) - 1] 
     268                    id = tuple([row[x] for x in indices]) 
     269                    if id not in cache: 
     270                        if added_fields: 
     271                            # Remove the identifier columns from the row. 
     272                            row = row[:len(row) - added_fields] 
    265273                        seen.append(row) 
    266274            return iter(seen) 
     
    291299                self.nextstore.reserve(unit) 
    292300            else: 
    293                 if unit.ID is None
    294                     unit.ID = unit.sequencer.next(cache.keys()) 
     301                if not unit.sequencer.valid_id(unit.identity())
     302                    unit.sequencer.assign(unit, cache.keys()) 
    295303            # Pickle the Unit to discard extraneous attributes, 
    296304            # and avoid identity issues. 
    297             cache[unit.ID] = pickle.dumps(unit) 
    298             self._recallTimes[unit.ID] = datetime.datetime.now() 
     305            id = unit.identity() 
     306            cache[id] = pickle.dumps(unit) 
     307            self._recallTimes[id] = datetime.datetime.now() 
    299308        finally: 
    300309            lock.release() 
     
    357366                now = datetime.datetime.now() 
    358367                for unit in self.nextstore.recall(unitClass, None): 
    359                     cache[unit.ID] = pickle.dumps(unit) 
    360                     self._recallTimes[unit.ID] = now 
     368                    id = unit.identity() 
     369                    cache[id] = pickle.dumps(unit) 
     370                    self._recallTimes[id] = now 
    361371             
    362372            return self._icached_units(cache, expr) 
  • trunk/storage/db.py

    r112 r113  
    923923                        in enumerate(col_defs)]) 
    924924         
    925         # Get specs on properties. Get the ID property first in case other 
    926         # fields depend upon it. See load_expanded, for example. 
     925        # Get specs on properties. Get the identifier properties 
     926        # first, in case other fields depend upon them. 
     927        # See load_expanded, for example. 
    927928        props = [] 
    928         for key in ['ID'] + [x for x in cls.properties() if x != "ID"]: 
     929        idnames = [prop.key for prop in cls.identifiers] 
     930        for key in idnames + [x for x in cls.properties() if x not in idnames]: 
    929931            if self.identifier_caseless: 
    930932                index, ftype = columns[key.lower()] 
     
    962964        Notice in particular that we do not use the auto-number or 
    963965        sequence generation capabilities within some databases, etc. 
    964         The ID should be supplied by UnitSequencers via reserve(). 
     966        The identifiers should be supplied by UnitSequencers via reserve(). 
    965967        """ 
    966968        cls = unit.__class__ 
     
    969971        self.reserve_lock.acquire() 
    970972        try: 
    971             if unit.ID is None
     973            if not unit.sequencer.valid_id(unit.identity())
    972974                # Examine all existing IDs and grant the "next" one. 
    973                 data, cols = self.fetch(u'SELECT %s FROM %s;' % (i('ID'), tablename)) 
     975                id_fields = [i(prop.key) for prop in cls.identifiers] 
     976                data, cols = self.fetch(u'SELECT %s FROM %s;' % 
     977                                        (', '.join(id_fields), tablename)) 
    974978                if data: 
    975979                    # sqlite 2, for example, has empty cols tuple if no data. 
    976980                    coerce = self.fromAdapter.coerce 
    977                     coltype = cols[0][1] 
    978                     expectedType = cls.property_type("ID") 
    979                     data = [coerce(row[0], coltype, expectedType) for row in data] 
    980                     unit.ID = unit.sequencer.next(data) 
    981                 else: 
    982                     unit.ID = unit.sequencer.next([]) 
     981                    coltypes = [cols[x][1] for x in xrange(len(cols))] 
     982                    expectedTypes = [prop.type for prop in cls.identifiers] 
     983                    newdata = [] 
     984                    for row in data: 
     985                        newrow = [] 
     986                        for x, cell in enumerate(row): 
     987                            newrow.append(coerce(cell, coltypes[x], 
     988                                                 expectedTypes[x])) 
     989                        newdata.append(newrow) 
     990                    data = newdata 
     991                    del newdata 
     992                cls.sequencer.assign(unit, data) 
    983993                del data 
    984994                del cols 
     
    9951005                    values.append(val) 
    9961006             
     1007            fields = u", ".join(fields) 
     1008            values = u", ".join(values) 
    9971009            self.execute('INSERT INTO %s (%s) VALUES (%s);' % 
    998                          (tablename, u", ".join(fields), 
    999                           u", ".join(values))) 
     1010                         (tablename, fields, values)) 
    10001011            unit.cleanse() 
    10011012        finally: 
    10021013            self.reserve_lock.release() 
     1014     
     1015    def id_clause(self, unit): 
     1016        i = self.identifier 
     1017        c = self.toAdapter.coerce 
     1018        idnames = [prop.key for prop in unit.identifiers] 
     1019        return " AND ".join(["%s = %s" % (i(key), c(getattr(unit, key))) 
     1020                             for key in idnames]) 
    10031021     
    10041022    def save(self, unit, forceSave=False): 
     
    10081026             
    10091027            parms = [] 
     1028            idnames = [prop.key for prop in cls.identifiers] 
    10101029            for key in cls.properties(): 
    1011                 if key != "ID"
     1030                if key not in idnames
    10121031                    subtype = self.expanded_columns.get((cls.__name__, key)) 
    10131032                    if subtype: 
     
    10171036                        parms.append('%s = %s' % (self.identifier(key), val)) 
    10181037             
    1019             sql = ('UPDATE %s SET %s WHERE %s = %s;' % 
     1038            sql = ('UPDATE %s SET %s WHERE %s;' % 
    10201039                   (self.tablename(unit), u", ".join(parms), 
    1021                     self.identifier("ID"), 
    1022                     self.toAdapter.coerce(unit.ID))) 
     1040                    self.id_clause(unit))) 
    10231041            self.execute(sql) 
    10241042            unit.cleanse() 
     
    10271045        """save_expanded(unit, key, subtype). Save list in separate table.""" 
    10281046        unitcls = unit.__class__ 
     1047        id = "_".join(map(str, unit.identity())) 
    10291048        table = self.identifier(self.prefix, "_", unitcls.__name__, 
    1030                                 "_", unit.ID, "_", key) 
     1049                                "_", id, "_", key) 
    10311050         
    10321051        # Just drop the old table and start with a new one. 
     
    10511070    def load_expanded(self, unit, key, subtype): 
    10521071        """load_expanded(unit, key, subtype). Load list from separate table.""" 
     1072        id = "_".join(map(str, unit.identity())) 
    10531073        table = self.identifier(self.prefix, "_", unit.__class__.__name__, 
    1054                                 "_", unit.ID, "_", key) 
     1074                                "_", id, "_", key) 
    10551075        try: 
    10561076            data, col_defs = self.fetch(u"SELECT EXPVAL FROM %s" % table) 
     
    10701090    def destroy(self, unit): 
    10711091        """destroy(unit). Delete the unit.""" 
    1072         self.execute(u'DELETE * FROM %s WHERE %s = %s;' % 
    1073                      (self.tablename(unit), self.identifier("ID"), 
    1074                       self.toAdapter.coerce(unit.ID))) 
     1092        self.execute(u'DELETE * FROM %s WHERE %s;' % 
     1093                     (self.tablename(unit), self.id_clause(unit))) 
    10751094     
    10761095    def view(self, cls, fields, expr=None): 
     
    11501169        basecls = firstcls = classes[0] 
    11511170        for cls in classes: 
    1152             # Place the ID property first in case others depend upon it. 
    1153             keys = ['ID'] + [k for k in cls.properties() if k != 'ID'] 
     1171            # Place the identifier properties first 
     1172            # in case others depend upon it. 
     1173            idnames = [prop.key for prop in cls.identifiers] 
     1174            keys = idnames + [k for k in cls.properties() if k not in idnames] 
    11541175            columns.extend([(cls, k) for k in keys]) 
    11551176             
  • trunk/storage/storemysql.py

    r112 r113  
    238238    def destroy(self, unit): 
    239239        """destroy(unit). Delete the unit.""" 
    240         self.execute(u'DELETE FROM %s WHERE %s = %s;' % 
    241                      (self.tablename(unit), self.identifier("ID"), 
    242                       self.toAdapter.coerce(unit.ID))) 
    243  
     240        self.execute(u'DELETE FROM %s WHERE %s;' % 
     241                     (self.tablename(unit), self.id_clause(unit))) 
     242 
  • trunk/storage/storeshelve.py

    r112 r113  
    1 import os 
    21try: 
    32    from bsddb._db import DBNoSuchFileError 
    43except ImportError: 
    54    DBNoSuchFileError = object() 
     5 
     6import os 
     7 
     8try: 
     9    import cPickle as pickle 
     10except ImportError: 
     11    import pickle 
     12 
    613import shelve 
    714import threading 
     
    7178     
    7279    def key(self, arg): 
    73         if isinstance(arg, unicode): 
    74             # str() means we can't use unicode with non-ascii char's. :( 
    75             return arg.encode('utf-8') 
    76         return str(arg) 
     80        return pickle.dumps(arg) 
    7781     
    7882    def reserve(self, unit): 
     
    8084        data, lock = self.shelf(unit.__class__) 
    8185        try: 
    82             if unit.ID is None: 
    83                 ids = [x['ID'] for x in data.itervalues()] 
    84                 unit.ID = unit.sequencer.next(ids) 
    85             data[self.key(unit.ID)] = unit._properties 
     86            if not unit.sequencer.valid_id(unit.identity()): 
     87                ids = [[row[prop.key] for prop in unit.identifiers] 
     88                       for row in data.itervalues()] 
     89                unit.sequencer.assign(unit, ids) 
     90            data[self.key(unit.identity())] = unit._properties 
    8691        finally: 
    8792            lock.release() 
     
    9398            data, lock = self.shelf(unit.__class__) 
    9499            try: 
    95                 data[self.key(unit.ID)] = unit._properties 
     100                data[self.key(unit.identity())] = unit._properties 
    96101            finally: 
    97102                lock.release() 
     
    102107        data, lock = self.shelf(unit.__class__) 
    103108        try: 
    104             del data[self.key(unit.ID)] 
     109            del data[self.key(unit.identity())] 
    105110        finally: 
    106111            lock.release() 
  • trunk/storage/storesqlite.py

    r112 r113  
    191191        """destroy(unit). Delete the unit.""" 
    192192        self.execute(u'DELETE FROM %s WHERE %s = %s;' % 
    193                      (self.tablename(unit), self.identifier("ID"), 
    194                       self.toAdapter.coerce(unit.ID))) 
     193                     (self.tablename(unit), self.id_clause(unit))) 
    195194     
    196195    def create_storage(self, unitClass): 
  • trunk/test/zoo_fixture.py

    r112 r113  
    22 
    33import datetime 
     4import os 
    45import threading 
    56import unittest 
     
    1920from dejavu import Unit, UnitProperty, ToOne, ToMany 
    2021from dejavu.test import tools 
     22from dejavu import engines 
    2123 
    2224 
     
    7173Vet.one_to_many('ID', Visit, 'VetID') 
    7274Animal.one_to_many('ID', Visit, 'AnimalID') 
     75 
    7376 
    7477 
     
    8386    else: 
    8487        Acreage = UnitProperty(float) 
     88     
     89    # Remove the ID property (inherited from Unit) from the Exhibit class. 
     90    ID = None 
     91    sequencer = dejavu.UnitSequencerNull() 
     92    identifiers = (ZooID, Name) 
    8593 
    8694Zoo.one_to_many('ID', Exhibit, 'ZooID') 
     
    121129                  ) 
    122130        box.memorize(WAP) 
     131        # The object should get an ID automatically. 
     132        self.assertNotEqual(WAP.ID, None) 
    123133         
    124134        SDZ = Zoo(Name = 'San Diego Zoo', 
     
    130140                  ) 
    131141        box.memorize(SDZ) 
     142        # The object should get an ID automatically. 
     143        self.assertNotEqual(SDZ.ID, None) 
    132144         
    133145        Biodome = Zoo(Name = u'Montr\xe9al Biod\xf4me', 
     
    523535            vet = visit.Vet() 
    524536            self.assertEqual(vet.ID, 1) 
     537     
     538    def test_A_engines(self): 
     539        box = arena.new_sandbox() 
     540         
     541        quadrupeds = box.recall(Animal, logic.filter(Legs=4)) 
     542        self.assertEqual(len(quadrupeds), 4) 
     543         
     544        eng = engines.UnitEngine() 
     545        box.memorize(eng) 
     546        eng.add_rule('CREATE', 1, "Animal") 
     547        eng.add_rule('FILTER', 1, logic.filter(Legs=4)) 
     548        self.assertEqual(eng.FinalClassName, "Animal") 
     549         
     550        qcoll = eng.take_snapshot() 
     551        self.assertEqual(len(qcoll), 4) 
     552        self.assertEqual(qcoll.EngineID, eng.ID) 
     553         
     554        eng.add_rule('TRANSFORM', 1, "Zoo") 
     555        self.assertEqual(eng.FinalClassName, "Zoo") 
     556         
     557        qcoll = eng.take_snapshot() 
     558        self.assertEqual(len(qcoll), 2) 
     559        zoos = qcoll.units() 
     560        zoos.sort(dejavu.sort('Name')) 
     561         
     562        SDZ = box.unit(Zoo, Name='San Diego Zoo') 
     563        WAP = box.unit(Zoo, Name='Wild Animal Park') 
     564        self.assertEqual(zoos, [SDZ, WAP]) 
     565         
     566        # Flush and start over 
     567        box.flush_all() 
     568        box = arena.new_sandbox() 
     569         
     570        eng = box.unit(engines.UnitEngine, ID=1) 
     571        self.assertEqual(len(eng.rules()), 3) 
     572        snaps = eng.snapshots() 
     573        self.assertEqual(len(snaps), 2) 
     574         
     575        self.assertEqual(snaps[0].Type, "Animal") 
     576        self.assertEqual(len(snaps[0]), 4) 
     577         
     578        self.assertEqual(snaps[1].Type, "Zoo") 
     579        self.assertEqual(len(snaps[1]), 2) 
     580        self.assertEqual(eng.last_snapshot(), snaps[1]) 
    525581 
    526582 
    527583arena = dejavu.Arena() 
     584 
     585def djvlog(message, flag): 
     586    """Dejavu logger (writes to error.log).""" 
     587    if flag & arena.logflags: 
     588        s = "%s %s" % (datetime.datetime.now().isoformat(), 
     589                       message.encode('utf8')) 
     590        fname = os.path.join(os.path.dirname(__file__), "djvtest.log") 
     591        f = open(fname, 'ab') 
     592        f.write(s + '\n') 
     593        f.close() 
    528594 
    529595def init(): 
    530596    global arena 
    531597    arena = dejavu.Arena() 
     598    arena.log = djvlog 
     599    arena.logflags = dejavu.LOGSQL 
    532600 
    533601def setup(SM_class, opts): 
     
    540608     
    541609    arena.register_all(globals()) 
    542      
    543     for cls in (Animal, Zoo, Exhibit, Vet, Visit): 
     610    engines.register_classes(arena) 
     611     
     612    for cls in (Animal, Zoo, Exhibit, Vet, Visit, engines.UnitEngine, 
     613                engines.UnitCollection, engines.UnitEngineRule): 
    544614        arena.create_storage(cls) 
    545615 
  • trunk/units.py

    r112 r113  
    111111 
    112112 
    113 # All Units currently must possess an 'ID' UnitProperty. The sequencing of 
    114 # IDs depends upon their type and the particular needs of the class. Pick 
    115 # one of these UnitSequencers to fit your subclass. 
    116  
    117 # At the moment, no ID sequences are allowed None as a value, since this 
    118 # signals a Unit which needs to be sequenced when memorized. In addition, 
    119 # you should aim to create new sequencers which generate IDs that obey 
    120 # the builtin max() and min() functions. 
     113# All Units must possess at least one UnitProperty which is an identifier. 
     114# The sequencing of identifiers depends upon their type and the particular 
     115# needs of the class. Pick one of these UnitSequencers to fit your subclass. 
     116# When creating new sequencers, you should aim to generate identifiers that 
     117# obey the builtin max() and min() functions. 
    121118 
    122119class UnitSequencerNull(object): 
    123     """A null sequencer for Unit IDs. Sequencing will error. 
    124      
    125     In many cases, ID values simply have no algorithmic sequence; 
     120    """A null sequencer for Unit identifiers. Sequencing will error. 
     121     
     122    In many cases, identifier values simply have no algorithmic sequence; 
    126123    for example, a set of Employee Units might use Social Security 
    127     Numbers for IDs (which you should never, ever do ;). 
     124    Numbers for identifiers (which you should never, ever do ;). 
    128125     
    129126    In other cases, sequencing will be best handled by custom algorithms 
     
    135132        self.type = type 
    136133     
    137     def next(self, sequence): 
     134    def valid_id(self, value): 
     135        for atom in value: 
     136            if atom is None: 
     137                return False 
     138        return True 
     139     
     140    def assign(self, unit, sequence): 
    138141        raise StopIteration("No sequence defined.") 
    139142 
    140143 
    141144class UnitSequencerInteger(object): 
    142     """A sequencer for Unit IDs, where id[i+1] == id[i] + 1.""" 
     145    """A sequencer for Unit identifiers, where id[i+1] == id[i] + 1.""" 
    143146     
    144147    def __init__(self, type=int, initial=1): 
     
    146149        self.initial = initial 
    147150     
    148     def next(self, sequence): 
     151    def valid_id(self, value): 
     152        return value != (None,) # and value >= self.initial? 
     153     
     154    def assign(self, unit, sequence): 
     155        newvalue= self.initial 
    149156        if sequence: 
    150157            m = max(sequence) 
    151             if m is not None
    152                 return m + 1 
    153         return self.initial 
     158            if m != (None,)
     159                newvalue = m[0] + 1 
     160        unit.identifiers[0].__set__(unit, newvalue) 
    154161 
    155162 
     
    157164    """UnitSequencerUnicode(type=unicode, width=6, 
    158165        range="abcdefghijklmnopqrstuvwxyz") 
    159     A sequencer for Unit IDs, where e.g. next(['abc']) == 'abd'.""" 
     166    A sequencer for Unit identifiers, where e.g. next(['abc']) == 'abd'.""" 
    160167     
    161168    def __init__(self, type=unicode, width=6, 
     
    165172        self.range = range 
    166173     
    167     def next(self, sequence): 
     174    def valid_id(self, value): 
     175        return value != (None,) 
     176     
     177    def assign(self, unit, sequence): 
    168178        r = self.range 
     179        newvalue = r[0] * self.width 
    169180        if sequence: 
    170             maxid = max(sequence) 
     181            maxid = max(sequence)[0] 
    171182            if len(maxid) != self.width: 
    172183                raise OverflowError("'%s' is not of width %s." % 
     
    180191                    break 
    181192            else: 
    182                 raise OverflowError("Next ID exceeds width %s." % self.width) 
    183             return maxid 
    184         return r[0] * self.width 
     193                raise OverflowError("Next identifier exceeds width %s." 
     194                                    % self.width) 
     195            newvalue = maxid 
     196        unit.identifiers[0].__set__(unit, newvalue) 
    185197 
    186198 
     
    279291                props[name] = val 
    280292             
     293            # Remove any properties from the parent class if requested 
     294            # (request by binding the parent's UnitProperty.key to None). 
     295            if name in props and val is None: 
     296                del props[name] 
     297                 
    281298            # Now grab any new UnitAssociations defined in this class. 
    282299            if isinstance(val, UnitAssociation): 
     
    331348    #       or even 
    332349    #     UnitSubclass.ID.type = unicode 
    333     # You will probably also want to override Unit.sequencer for the class. 
    334350    ID = UnitProperty(int, index=True) 
    335351    sequencer = UnitSequencerInteger() 
     352    identifiers = (ID,) 
    336353     
    337354    def __init__(self, **kwargs): 
     
    347364        for k, v in kwargs.iteritems(): 
    348365            setattr(self, k, v) 
     366     
     367    def repress(self): 
     368        """repress() -> Remove this Unit from memory (do not destroy).""" 
     369        self.sandbox.repress(self) 
     370     
     371    def forget(self): 
     372        """forget() -> Destroy this Unit.""" 
     373        self.sandbox.forget(self) 
     374     
     375    def __copy__(self): 
     376        newUnit = self.__class__() 
     377        for key in self.__class__.properties(): 
     378            if key not in self.identifiers: 
     379                newUnit._properties[key] = self._properties[key] 
     380        for prop in self.identifiers: 
     381            prop.__set__(newUnit, None) 
     382        newUnit.sandbox = None 
     383        return newUnit 
     384     
     385    #                        Pickle data                         # 
     386     
     387    def __getstate__(self): 
     388        return (self._properties, self._initial_property_hash) 
     389     
     390    def __setstate__(self, state): 
     391        self.sandbox = None 
     392        self._properties, self._initial_property_hash = state 
     393     
     394     
     395    #                         Properties                         # 
     396     
     397    def identity(self): 
     398        # Must be immutable for use as a dictionary key. 
     399        return tuple([prop.__get__(self) for prop in self.identifiers]) 
    349400     
    350401    def _property_hash(self): 
     
    360411    def cleanse(self): 
    361412        self._initial_property_hash = self._property_hash() 
    362      
    363     def repress(self): 
    364         """repress() -> Remove this Unit from memory (do not destroy).""" 
    365         self.sandbox.repress(self) 
    366      
    367     def forget(self): 
    368         """forget() -> Destroy this Unit.""" 
    369         self.sandbox.forget(self) 
    370      
    371     def __copy__(self): 
    372         newUnit = self.__class__() 
    373         for key in self.__class__.properties(): 
    374             if key != u'ID': 
    375                 newUnit._properties[key] = self._properties[key] 
    376         newUnit.ID = None 
    377         newUnit.sandbox = None 
    378         return newUnit 
    379      
    380     #                        Pickle data                         # 
    381      
    382     def __getstate__(self): 
    383         return (self._properties, self._initial_property_hash) 
    384      
    385     def __setstate__(self, state): 
    386         self.sandbox = None 
    387         self._properties, self._initial_property_hash = state 
    388      
    389      
    390     #                         Properties                         # 
    391413     
    392414    def set_property(cls, key, type=unicode, index=False,