Changeset 113
- Timestamp:
- 11/29/05 04:49:56
- Files:
-
- trunk/arenas.py (modified) (10 diffs)
- trunk/doc/advanced.html (modified) (2 diffs)
- trunk/doc/managing.html (modified) (3 diffs)
- trunk/doc/modeling.html (modified) (3 diffs)
- trunk/engines.py (modified) (9 diffs)
- trunk/storage/__init__.py (modified) (6 diffs)
- trunk/storage/db.py (modified) (10 diffs)
- trunk/storage/storemysql.py (modified) (1 diff)
- trunk/storage/storeshelve.py (modified) (5 diffs)
- trunk/storage/storesqlite.py (modified) (1 diff)
- trunk/test/zoo_fixture.py (modified) (8 diffs)
- trunk/units.py (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/arenas.py
r112 r113 198 198 unit.sandbox = self 199 199 200 # Ask the store to accept the unit, assigning it an ID if201 # necessary. The store should also call unit.cleanse()202 # if itsaves 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. 203 203 self.arena.storage(cls).reserve(unit) 204 204 205 205 # 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) 208 209 209 210 # Do this at the end of the func, since most on_memorize 210 # will want to have an IDwhen called.211 # will want to have an identity when called. 211 212 if hasattr(unit, "on_memorize"): 212 213 unit.on_memorize() … … 216 217 cls = unit.__class__ 217 218 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) 219 221 self.arena.storage(cls).destroy(unit) 220 222 221 del self._cache(cls)[ unit.ID]223 del self._cache(cls)[id] 222 224 223 225 if hasattr(unit, "on_forget"): … … 240 242 fc.co_names[-1] == 'ID'): 241 243 ID = fc.co_consts[-1] 242 unit = cache.get( ID)244 unit = cache.get((ID,)) 243 245 if unit is not None: 244 246 # Do NOT call on_recall here. That should be called … … 249 251 # Query Cache and Storage. 250 252 for unit in self.arena.storage(cls).recall(cls, expr): 251 ID = unit.ID253 id = unit.identity() 252 254 # Very important that we check for existing unit, as its 253 255 # state may have changed in memory but not in storage. 254 256 # Make sure the cache lookup and get happens atomically. 255 existing = cache.get( ID)257 existing = cache.get(id) 256 258 if existing: 257 259 yield existing … … 259 261 unit.sandbox = self 260 262 confirmed = True 261 cache[ ID] = unit263 cache[id] = unit 262 264 if hasattr(unit, 'on_recall'): 263 265 try: … … 309 311 for index in xrange(len(unitset)): 310 312 unit = unitset[index] 311 ID = unit.ID313 id = unit.identity() 312 314 cache = self._cache(unit.__class__) 313 if IDin cache:315 if id in cache: 314 316 # Keep the unit which is in our cache! 315 unitset[index] = cache[ ID]317 unitset[index] = cache[id] 316 318 else: 317 cache[ ID] = unit319 cache[id] = unit 318 320 unit.sandbox = self 319 321 if hasattr(unit, 'on_recall'): … … 354 356 yield tuple([getattr(unit, attr) for attr in attrs]) 355 357 356 # Add the ID attribute if not present. This is necessary to357 # 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. 358 360 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)) 362 368 363 369 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] 368 375 yield row 369 376 … … 399 406 def count(self, cls, expr): 400 407 """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)) 402 410 403 411 #################################### … … 421 429 """repress(unit). Remove unit from cache (but don't destroy).""" 422 430 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) 424 433 425 434 if hasattr(unit, "on_repress"): … … 429 438 self.arena.storage(cls).save(unit) 430 439 431 del self._cache(cls)[ unit.ID]440 del self._cache(cls)[id] 432 441 433 442 def flush_all(self): trunk/doc/advanced.html
r112 r113 249 249 to create one (unless this is a pass-through SM). Grab all existing 250 250 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 253 255 <tt>threading.Lock</tt>.</li> 254 256 <li><b>save</b>: If <tt>not unit.dirty()</tt>, you can exit. Otherwise, … … 259 261 call <tt>unit.cleanse()</tt> to mark the Unit as no longer dirty. 260 262 </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> 263 265 <li><b>recall</b>: If no Expression is supplied, return all Units. 264 266 Otherwise, use a decompiler to produce SQL which you can then use trunk/doc/managing.html
r112 r113 274 274 <h4>Collections: Lists of Units</h4> 275 275 <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>276 of Units, or rather, a list of Unit identifiers. You use its <tt>Type</tt> 277 277 property to indicate the class of the indexed Units. That value should be 278 278 the <b>name</b> of the Unit Class, <b>not</b> the class object itself … … 295 295 which will 296 296 look 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 been298 destroyed since the list was built. The <tt>units</tt> method simply297 stores identifiers, it is possible that one of the indexed Units may have 298 been destroyed since the list was built. The <tt>units</tt> method simply 299 299 passes over these "phantom" Units. You can inspect the full list of IDs 300 300 in the Collection (whether they reference existing Units or not) with … … 337 337 your users, "Engine #23569 is an 'Armadillo' engine," when it produces 338 338 Collections 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 added340 any Rules.</p>339 set this value manually is when you first create the Engine, before you 340 have added any Rules.</p> 341 341 342 342 <h4><a name='unitenginerules'>Rules</a></h4> trunk/doc/modeling.html
r112 r113 62 62 63 63 <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 65 their UnitProperties which define the uniqueness of a Unit. The 66 <tt>Unit</tt> base class possesses a single Unit Property, an int 67 named '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 69 to the actual UnitProperty class. If you wish to use identifiers 70 of a different number, types, or names, simply replace the 71 <tt>identifiers</tt> attribute in your subclass:</p> 72 67 73 <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 83 each Unit within the system is unique. You should consider any 84 UnitProperty which is one of the identifiers to be read-only 85 after a Unit has been memorized.</p> 71 86 72 87 <h4>Creating and Populating Properties</h4> … … 260 275 261 276 <h4>Sequencing</h4> 262 <p>Every <tt>Unit</tt> has an <tt>ID</tt> property. The default ID property277 <p>Every <tt>Unit</tt> has one or more identifiers. The default ID property 263 278 is 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 will265 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>279 you like. As long as you provide your own identifier values for Units, 280 nothing will break--you can memorize and recall Units without problems. 281 However, if you memorize a Unit with an ID of <tt>None</tt>, the Sandbox 282 may attempt to provide an ID for it.</p> 268 283 269 284 <p>The <tt>Unit</tt> base class possesses a <tt>sequencer</tt> attribute … … 429 444 430 445 <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> 446 function by passing <tt>attrs = [prop.key for prop in cls.identifiers]</tt>. 447 Sandboxes provide a <tt class='def'>count(cls, expr)</tt> method which does 448 just this.</p> 433 449 434 450 trunk/engines.py
r112 r113 19 19 20 20 class UnitCollection(dejavu.Unit): 21 """A Set of Unit IDs.21 """A Set of Unit identifiers. 22 22 23 23 Type: Unit Type of all Units referenced by this collection. … … 91 91 def units(self, quota=None): 92 92 cls = self.unit_class() 93 idnames = [prop.key for prop in cls.identifiers] 94 93 95 output = [] 94 96 self.acquire() … … 97 99 if quota and i >= quota: 98 100 break 99 unit = self.sandbox.unit(cls, ID=eachID)101 unit = self.sandbox.unit(cls, **dict(zip(idnames, eachID))) 100 102 if unit: 101 103 output.append(unit) … … 173 175 174 176 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 units177 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. 178 180 179 181 FILTER: 180 182 If the Operation is 'FILTER', the Operand shall be a 181 logic.Expression, and the snapshot will consist of the IDs of182 Units which match the Expression.183 logic.Expression, and the snapshot will consist of the 184 identifiers of Units which match the Expression. 183 185 184 186 So, a typical Engine might have a set of rules which look like: … … 452 454 mem = A.Members 453 455 for unit in self.sandbox.recall(cls): 454 id = unit. ID456 id = unit.identity() 455 457 if id not in B.Members: 456 458 mem.append(id) … … 474 476 A.universal = False 475 477 for unit in self.sandbox.recall(cls, expr): 476 id = unit. ID478 id = unit.identity() 477 479 if id not in mem: 478 480 mem.append(id) 479 481 else: 480 482 newset = [] 483 idnames = [prop.key for prop in cls.identifiers] 481 484 for id in mem: 482 unit = self.sandbox.unit(cls, ID=id)485 unit = self.sandbox.unit(cls, **dict(zip(idnames, id))) 483 486 if unit and expr(unit): 484 487 newset.append(id) … … 534 537 mem = A.Members 535 538 for unit in self.sandbox.recall(cls): 536 mem.append(unit. ID)539 mem.append(unit.identity()) 537 540 finally: 538 541 A.release() … … 566 569 farUnits = [farUnits] 567 570 for farUnit in farUnits: 568 farid = farUnit. ID571 farid = farUnit.identity() 569 572 if farid not in newset: 570 573 newset.append(farid) 571 574 A.universal = False 572 575 else: 576 idnames = [prop.key for prop in cls.identifiers] 573 577 for id in A.Members: 574 unit = self.sandbox.unit(cls, ID=id)578 unit = self.sandbox.unit(cls, **dict(zip(idnames, id))) 575 579 if unit: 576 580 farUnits = ua.__get__(unit)() … … 581 585 farUnits = [farUnits] 582 586 for farUnit in farUnits: 583 farid = farUnit. ID587 farid = farUnit.identity() 584 588 if farid not in newset: 585 589 newset.append(farid) trunk/storage/__init__.py
r112 r113 181 181 if self.nextstore: 182 182 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: 184 185 # Pickle the Unit to discard extraneous attributes, 185 186 # and avoid identity issues. 186 cache[ unit.ID] = pickle.dumps(unit)187 self._recallTimes[ unit.ID] = currentTime187 cache[id] = pickle.dumps(unit) 188 self._recallTimes[id] = currentTime 188 189 189 190 # Only add to matches if it wasn't already in our 190 191 # cache (because stored units may have stale data). 191 if unit.IDnot in matches:192 matches[ unit.ID] = unit192 if id not in matches: 193 matches[id] = unit 193 194 194 195 return iter(matches.values()) … … 208 209 try: 209 210 cache = self._caches[unit.__class__] 210 cache[unit. ID] = pickle.dumps(unit)211 cache[unit.identity()] = pickle.dumps(unit) 211 212 finally: 212 213 lock.release() … … 217 218 lock = self._get_lock(unitClass) 218 219 try: 220 id = unit.identity() 219 221 cache = self._caches[unitClass] 220 222 if self.nextstore: 221 223 self.nextstore.destroy(unit) 222 224 try: 223 del cache[ unit.ID]225 del cache[id] 224 226 except KeyError: 225 227 pass 226 228 try: 227 del self._recallTimes[ unit.ID]229 del self._recallTimes[id] 228 230 except KeyError: 229 231 pass … … 251 253 if self.nextstore: 252 254 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. 255 258 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)) 259 266 260 267 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] 265 273 seen.append(row) 266 274 return iter(seen) … … 291 299 self.nextstore.reserve(unit) 292 300 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()) 295 303 # Pickle the Unit to discard extraneous attributes, 296 304 # 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() 299 308 finally: 300 309 lock.release() … … 357 366 now = datetime.datetime.now() 358 367 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 361 371 362 372 return self._icached_units(cache, expr) trunk/storage/db.py
r112 r113 923 923 in enumerate(col_defs)]) 924 924 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. 927 928 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]: 929 931 if self.identifier_caseless: 930 932 index, ftype = columns[key.lower()] … … 962 964 Notice in particular that we do not use the auto-number or 963 965 sequence generation capabilities within some databases, etc. 964 The IDshould be supplied by UnitSequencers via reserve().966 The identifiers should be supplied by UnitSequencers via reserve(). 965 967 """ 966 968 cls = unit.__class__ … … 969 971 self.reserve_lock.acquire() 970 972 try: 971 if unit.ID is None:973 if not unit.sequencer.valid_id(unit.identity()): 972 974 # 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)) 974 978 if data: 975 979 # sqlite 2, for example, has empty cols tuple if no data. 976 980 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) 983 993 del data 984 994 del cols … … 995 1005 values.append(val) 996 1006 1007 fields = u", ".join(fields) 1008 values = u", ".join(values) 997 1009 self.execute('INSERT INTO %s (%s) VALUES (%s);' % 998 (tablename, u", ".join(fields), 999 u", ".join(values))) 1010 (tablename, fields, values)) 1000 1011 unit.cleanse() 1001 1012 finally: 1002 1013 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]) 1003 1021 1004 1022 def save(self, unit, forceSave=False): … … 1008 1026 1009 1027 parms = [] 1028 idnames = [prop.key for prop in cls.identifiers] 1010 1029 for key in cls.properties(): 1011 if key != "ID":1030 if key not in idnames: 1012 1031 subtype = self.expanded_columns.get((cls.__name__, key)) 1013 1032 if subtype: … … 1017 1036 parms.append('%s = %s' % (self.identifier(key), val)) 1018 1037 1019 sql = ('UPDATE %s SET %s WHERE %s = %s;' %1038 sql = ('UPDATE %s SET %s WHERE %s;' % 1020 1039 (self.tablename(unit), u", ".join(parms), 1021 self.identifier("ID"), 1022 self.toAdapter.coerce(unit.ID))) 1040 self.id_clause(unit))) 1023 1041 self.execute(sql) 1024 1042 unit.cleanse() … … 1027 1045 """save_expanded(unit, key, subtype). Save list in separate table.""" 1028 1046 unitcls = unit.__class__ 1047 id = "_".join(map(str, unit.identity())) 1029 1048 table = self.identifier(self.prefix, "_", unitcls.__name__, 1030 "_", unit.ID, "_", key)1049 "_", id, "_", key) 1031 1050 1032 1051 # Just drop the old table and start with a new one. … … 1051 1070 def load_expanded(self, unit, key, subtype): 1052 1071 """load_expanded(unit, key, subtype). Load list from separate table.""" 1072 id = "_".join(map(str, unit.identity())) 1053 1073 table = self.identifier(self.prefix, "_", unit.__class__.__name__, 1054 "_", unit.ID, "_", key)1074 "_", id, "_", key) 1055 1075 try: 1056 1076 data, col_defs = self.fetch(u"SELECT EXPVAL FROM %s" % table) … … 1070 1090 def destroy(self, unit): 1071 1091 """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))) 1075 1094 1076 1095 def view(self, cls, fields, expr=None): … … 1150 1169 basecls = firstcls = classes[0] 1151 1170 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] 1154 1175 columns.extend([(cls, k) for k in keys]) 1155 1176 trunk/storage/storemysql.py
r112 r113 238 238 def destroy(self, unit): 239 239 """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 os2 1 try: 3 2 from bsddb._db import DBNoSuchFileError 4 3 except ImportError: 5 4 DBNoSuchFileError = object() 5 6 import os 7 8 try: 9 import cPickle as pickle 10 except ImportError: 11 import pickle 12 6 13 import shelve 7 14 import threading … … 71 78 72 79 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) 77 81 78 82 def reserve(self, unit): … … 80 84 data, lock = self.shelf(unit.__class__) 81 85 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 86 91 finally: 87 92 lock.release() … … 93 98 data, lock = self.shelf(unit.__class__) 94 99 try: 95 data[self.key(unit. ID)] = unit._properties100 data[self.key(unit.identity())] = unit._properties 96 101 finally: 97 102 lock.release() … … 102 107 data, lock = self.shelf(unit.__class__) 103 108 try: 104 del data[self.key(unit. ID)]109 del data[self.key(unit.identity())] 105 110 finally: 106 111 lock.release() trunk/storage/storesqlite.py
r112 r113 191 191 """destroy(unit). Delete the unit.""" 192 192 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))) 195 194 196 195 def create_storage(self, unitClass): trunk/test/zoo_fixture.py
r112 r113 2 2 3 3 import datetime 4 import os 4 5 import threading 5 6 import unittest … … 19 20 from dejavu import Unit, UnitProperty, ToOne, ToMany 20 21 from dejavu.test import tools 22 from dejavu import engines 21 23 22 24 … … 71 73 Vet.one_to_many('ID', Visit, 'VetID') 72 74 Animal.one_to_many('ID', Visit, 'AnimalID') 75 73 76 74 77 … … 83 86 else: 84 87 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) 85 93 86 94 Zoo.one_to_many('ID', Exhibit, 'ZooID') … … 121 129 ) 122 130 box.memorize(WAP) 131 # The object should get an ID automatically. 132 self.assertNotEqual(WAP.ID, None) 123 133 124 134 SDZ = Zoo(Name = 'San Diego Zoo', … … 130 140 ) 131 141 box.memorize(SDZ) 142 # The object should get an ID automatically. 143 self.assertNotEqual(SDZ.ID, None) 132 144 133 145 Biodome = Zoo(Name = u'Montr\xe9al Biod\xf4me', … … 523 535 vet = visit.Vet() 524 536 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]) 525 581 526 582 527 583 arena = dejavu.Arena() 584 585 def 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() 528 594 529 595 def init(): 530 596 global arena 531 597 arena = dejavu.Arena() 598 arena.log = djvlog 599 arena.logflags = dejavu.LOGSQL 532 600 533 601 def setup(SM_class, opts): … … 540 608 541 609 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): 544 614 arena.create_storage(cls) 545 615 trunk/units.py
r112 r113 111 111 112 112 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. 121 118 122 119 class UnitSequencerNull(object): 123 """A null sequencer for Unit IDs. Sequencing will error.124 125 In many cases, IDvalues 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; 126 123 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 ;). 128 125 129 126 In other cases, sequencing will be best handled by custom algorithms … … 135 132 self.type = type 136 133 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): 138 141 raise StopIteration("No sequence defined.") 139 142 140 143 141 144 class 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.""" 143 146 144 147 def __init__(self, type=int, initial=1): … … 146 149 self.initial = initial 147 150 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 149 156 if sequence: 150 157 m = max(sequence) 151 if m is not None:152 return m+ 1153 return self.initial158 if m != (None,): 159 newvalue = m[0] + 1 160 unit.identifiers[0].__set__(unit, newvalue) 154 161 155 162 … … 157 164 """UnitSequencerUnicode(type=unicode, width=6, 158 165 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'.""" 160 167 161 168 def __init__(self, type=unicode, width=6, … … 165 172 self.range = range 166 173 167 def next(self, sequence): 174 def valid_id(self, value): 175 return value != (None,) 176 177 def assign(self, unit, sequence): 168 178 r = self.range 179 newvalue = r[0] * self.width 169 180 if sequence: 170 maxid = max(sequence) 181 maxid = max(sequence)[0] 171 182 if len(maxid) != self.width: 172 183 raise OverflowError("'%s' is not of width %s." % … … 180 191 break 181 192 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) 185 197 186 198 … … 279 291 props[name] = val 280 292 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 281 298 # Now grab any new UnitAssociations defined in this class. 282 299 if isinstance(val, UnitAssociation): … … 331 348 # or even 332 349 # UnitSubclass.ID.type = unicode 333 # You will probably also want to override Unit.sequencer for the class.334 350 ID = UnitProperty(int, index=True) 335 351 sequencer = UnitSequencerInteger() 352 identifiers = (ID,) 336 353 337 354 def __init__(self, **kwargs): … … 347 364 for k, v in kwargs.iteritems(): 348 365 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]) 349 400 350 401 def _property_hash(self): … … 360 411 def cleanse(self): 361 412 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 = None377 newUnit.sandbox = None378 return newUnit379 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 = None387 self._properties, self._initial_property_hash = state388 389 390 # Properties #391 413 392 414 def set_property(cls, key, type=unicode, index=False,
