PageRenderTime 919ms CodeModel.GetById 222ms app.highlight 504ms RepoModel.GetById 179ms app.codeStats 1ms

/pod/core.py

http://pickled-object-database.googlecode.com/
Python | 879 lines | 721 code | 103 blank | 55 comment | 120 complexity | 1e4c5fa9f0b1da70c445ba1691086df8 MD5 | raw file
  1import os
  2import sys
  3import time
  4import exceptions
  5import warnings
  6import inspect
  7
  8import db
  9import typed
 10
 11from query import Query, RawQuery
 12
 13import fn
 14
 15class PodMetaError(exceptions.BaseException):
 16    pass
 17
 18class PodNoDatabaseError(exceptions.BaseException):
 19    pass
 20
 21class Meta(type):
 22    
 23    POD_RESV_SET = set(['id', 'pod', 'store', 'where'])
 24    POD_GETA_SET = set(['__new__', '__instancecheck__', 'pod', 'where', '__class__', '__dict__']) 
 25    POD_SETI_SET = set(['__class__', 'id'])
 26    
 27    def __init__(cls, name, bases, dict):      
 28        
 29        if cls.__module__ == Meta.__module__:
 30            # We don't want to create tables and so on for the base classes defined in this module.

 31            return
 32
 33        if len(bases) > 1:
 34            raise PodMetaError, "Currently, pod does not support multiple inheritance . . . "
 35                
 36        for key in Meta.POD_RESV_SET:
 37            if key in dict:
 38                raise PodMetaError, "'" + key + "' is a reserved class variable and cannot be defined in class " + str(cls)
 39              
 40        cls_pod = type(cls).Pod(cls=cls, parent=bases[0] if bases[0] is not Object else None)
 41        type.__setattr__(cls, 'pod',   cls_pod)        
 42        type.__setattr__(cls, 'where', type(cls).Where(cls_pod = cls_pod))
 43                    
 44    def __getattribute__(cls, key):
 45        
 46        if key in Meta.POD_GETA_SET or key in type.__getattribute__(cls, '__dict__'):
 47            return type.__getattribute__(cls, key)     
 48        elif key == 'store':
 49            store = db.Store(db=type.__getattribute__(cls, 'pod').db, prefix=type.__getattribute__(cls, 'pod').table + '_store_')
 50            type.__setattr__(cls, 'store', store)
 51            return store
 52        elif key == 'id':
 53            id = typed.Int(index=False, cls_pod=type.__getattribute__(cls, 'pod'), name='id')
 54            type.__setattr__(cls, 'id', id)
 55            return id
 56        else:
 57            return type.__getattribute__(cls, key)
 58    
 59    def __setattr__(cls, key, value):
 60        
 61        # Here, __setattr__ is only performing error checking because dynamic creation of pod_types has been removed . . . 

 62        
 63        if key in Meta.POD_RESV_SET:
 64            raise PodMetaError, "Key '" + key + "' is a reserved attribute of pod classes . . . "
 65        
 66        if isinstance(value, typed.Typed):
 67            raise PodMetaError, "You cannot create a pod_type '" + key + "' dynamically -- please add it to the class header . . . "
 68        
 69        if isinstance(getattr(cls, key, None), typed.Typed):
 70            raise PodMetaError, "Attr '" + key + "' is of type pod.typed.Typed -- you cannot alter this attribute dynamically -- it must be performed in class header . . ."
 71        
 72        return type.__setattr__(cls, key, value)
 73        
 74    def __iter__(cls):
 75        return Query(where=cls).__iter__()
 76                        
 77    def execute(cls, query, args=()):
 78        return cls.pod.cursor.execute(query.replace("cls_table ", cls.pod.table + " ").replace("cls_table_dict", cls.pod.table_dict), args)
 79
 80    def executemany(cls, query, args=()):
 81        return cls.pod.cursor.executemany(query.replace("cls_table ", cls.pod.table + " ").replace("cls_table_dict", cls.pod.table_dict), args)
 82        
 83    def migrate(cls, new_cls):
 84        
 85        if not isinstance(new_cls, Meta):
 86            raise PodMetaError, "New class must be of type pod.Object"
 87
 88        for obj in Query(where=cls, child_classes = False):
 89            obj.full_load()
 90            new_cls(**obj.copy())
 91            
 92        cls.drop(child_classes = False)
 93        
 94    def drop(cls, child_classes = True):
 95        cls.pod.table_drop(child_classes = child_classes)
 96    
 97    def clear_all(cls, child_classes = True):
 98        cls.pod.table_clear(child_classes = child_classes)
 99        
100    def get_count(cls, child_classes=True, count=0):
101        get_one = RawQuery(select=fn.count(cls.id) >> 'count').get_one()
102
103        count = get_one.count + count if get_one else count
104        
105        if child_classes:
106            for child_pod in cls.pod.child_pods:
107                count = child_pod.cls.get_count(child_classes=child_classes, count=count)
108        return count
109        
110    def get_db(cls):
111        return type.__getattribute__(cls, 'pod').db
112    
113    class Pod(object):
114    
115        def __init__(self, cls, parent):
116            # pass ins

117            self.cls = cls
118            self.parent_pod = getattr(parent, 'pod', None)
119            self.db = None
120            self.id = None
121            
122            self.table = self.get_table_name()
123                    
124            # Init vars

125            self.typed_group = {}
126            self.type_callbacks_create = set()
127            self.type_callbacks_load = set()
128            self.child_pods = set()  
129            
130            # First, update parent and add any pod_types a parent has to your local dictionary. 

131            # The reason you need a local copy of a pod_type is because if you have a ParentClass, ChildClass and a pod_type called 'name'

132            # you want ParentClass.name == 'Fred' to return all objects with a name of 'Fred' but ChildClass.name == 'Fred' to 

133            # only return ChildClass object with a name == 'Fred'.  type_add_parents takes care of this . . .

134
135            # This updates all the pod_types passed in from the class header. 

136            for name, pod_type in self.cls.__dict__.iteritems():
137                 if isinstance(pod_type, typed.Typed):
138                    self.typed_group[name] = pod_type
139                    pod_type.on_pod_init(cls_pod=self, name=name)
140
141            if self.parent_pod is not None:
142                object.__getattribute__(self.parent_pod, 'child_pods').add(self)
143                for name, pod_type in object.__getattribute__(self.parent_pod, 'typed_group').iteritems():
144                    if name in self.cls.__dict__:
145                        raise PodMetaError, "Class " + str(self.cls) + " is trying to add pod_type " + name + ", but this pod_type already defined in parent " + str(object.__getattribute__(self.parent_pod, 'cls')) + " . . ."
146                    else:
147                        pod_type = pod_type.get_deep_copy(cls_pod=self, name=name)
148                        type.__setattr__(self.cls, name, pod_type)
149                        self.typed_group[name] = pod_type
150
151            # register table name

152            if db.register.global_db is None:    # This means no db connection has been made yet

153                db.register.pods_to_attach[self.table] = self
154                self.__class__ = type.__getattribute__(cls, 'PodNoDatabase')
155            elif db.register.global_db is False: # False means db connection was made with 'attach' parameter

156                self.db = self.get_parent_db()
157            elif db.register.global_db:
158                self.attach_to_db(db = db.register.global_db)
159                                    
160        def __getstate__(self):
161            raise PodMetaError, "You cannot pickle a Pod CLASS pod._Meta object . . . "
162
163        def get_parent_db(self):
164            
165            if self.parent_pod:
166                if self.parent_pod.db:
167                    db = self.parent_pod.db if self.parent_pod.db is not None else self.parent_pod.get_parent_db()
168                    self.attach_to_db(db = db)
169                    return db
170            else:
171                return None
172        
173        def attach_to_db(self, db):
174            
175            # If the local 'self.db' is not set, set it to the current and update all your children. 

176            # If it is already set, that's okay as long as the two databases match. 

177            if self.db is None:
178
179                self.table_dict = self.table + '_kvdict'
180                
181                self.db         = db               
182                self.cursor     = db.get_new_cursor(cls_pod = self)
183                
184                table_state = self.cursor.table_state_get()
185                
186                if table_state is None:   
187                    self.table_create()   # Note, this calls 'activate_on_get_table_id' at the right place . . . 

188                else:
189                    self.id      = table_state['id'] 
190                    index_kv_old = table_state['index_kv']
191                    
192                    self.index_kv = getattr(self.cls, 'POD_DYNAMIC_INDEX', self.db.dynamic_index)                        
193                    self.mtime    = self.get_mtime(source_file=inspect.getsourcefile(self.cls))
194        
195                    self.activate_on_get_table_id()
196                            
197                    if (int(table_state['mtime']) != self.mtime) or (self.index_kv != index_kv_old):
198                        self.table_check_for_changes(index_kv_old = index_kv_old)
199 
200                for child_pod in self.child_pods:
201                    child_pod.attach_to_db(db = db)
202                    
203            elif db is not self.db:
204                raise PodMetaError, "Class '" + str(self.cls) + "' is already attached to a database"
205
206        def activate_on_get_table_id(self):
207            """ This functions is only called by 1) activate or 2) table_create """
208            # This is used by both activate and table_create

209            self.db.cache.class_pods[self.id] = self
210            self.cache = {}      
211            self.zombies = set()    
212            self.fulls   = set()  
213            for pod_type in self.typed_group.itervalues():
214                pod_type.on_db_attach(db=self.db, cls_pod = self)
215             
216        def clear(self):
217            self.cache.clear()
218            self.zombies.clear()
219            self.fulls.clear()
220     
221        def get_table_name(self):
222            #

223            # The table name is: 

224            #   1. The module path plus the class name, for example myapp_models_Person. 

225            #   2. Just the class name (e.g. just Person) if the user has set POD_TABLE_NAME set to True

226            #      in class header. 

227            #   3. POD_TABLE_NAME if it's set to a string in the class header

228            #

229            #   Full name is harder to read if working with sql table directly, but 

230            #   uses full module name so it is less likely to have a namespace collision. 

231            #

232            cls_name = type.__getattribute__(self.cls, '__name__')
233            pod_table_name = type.__getattribute__(self.cls, '__dict__').get('POD_TABLE_NAME', None)
234            if pod_table_name is None:
235                return '_'.join(self.cls.__module__.split('.') + [cls_name])
236            elif pod_table_name is True:
237                return cls_name
238            else:
239                return pod_table_name
240        
241        def get_mtime(self, source_file=None):
242            try:
243                return int(os.path.getmtime(source_file)) if source_file else int(time.time())
244            except:
245                if source_file[-7:] == 'list.py' or source_file[-7:] == 'dict.py' or source_file[-6:] == 'set.py':  
246                    return 0
247                else:                                     
248                    raise PodMetaError, "Cannot get mtime on file '" + source_file + "' . . ."
249            
250        """ TABLE OPERATIONS """
251        def table_create(self):
252
253            # First, get the variables . . . 

254            self.mtime = self.get_mtime(source_file=inspect.getsourcefile(self.cls))
255            self.index_kv = type.__getattribute__(self.cls, '__dict__').get('POD_DYNAMIC_INDEX', self.db.dynamic_index)
256
257            self.id = self.cursor.create_class_tables()
258            if self.index_kv:
259                self.cursor.index_kv_add()
260
261            self.activate_on_get_table_id()
262                        
263            for name, pod_type in self.typed_group.iteritems():
264                # This will call 'sql_table_state_set'

265                self.type_create(name=name, pod_type=pod_type, is_new=True)
266            
267            if len(self.typed_group) == 0:
268                # if len == 0, the iteration above will not call 'sql_table_state_set', so call it. 

269                self.cursor.table_state_set()
270
271        def table_drop(self, child_classes = True):
272            if self.id:
273                self.cursor.drop_class_tables()
274                self.db.commit(clear_cache=True, close=False)
275                if child_classes:
276                    for child_pod in self.child_pods:
277                        child_pod.table_drop()
278
279        def table_clear(self, child_classes=True):
280            if self.id:                     
281                self.clear()   
282                self.cursor.clear_class_tables()
283                if child_classes:
284                    for child_pod in self.child_pods:
285                        child_pod.table_clear()
286
287        def table_check_for_changes(self, index_kv_old, copy_dropped_to_dict=True):
288
289            if self.index_kv is True and index_kv_old is False:
290                self.cursor.index_kv_add()
291                self.table_change_msg(msg='Adding index for dynamic attributes . . . ')
292            elif self.index_kv is False and index_kv_old is True:
293                self.cursor.index_kv_drop()
294                self.table_change_msg(msg='Dropping index for dynamic attributes . . . ')
295
296            old_pod_types = self.cursor.get_old_pod_types()
297            set_old_pod_types = set(old_pod_types.keys())
298            
299            for name, pod_type in old_pod_types.iteritems():
300                pod_type.on_pod_init(cls_pod=self, name=name)
301            
302            if set(self.type_get_current_db_pod_types()) != set_old_pod_types:  
303                raise PodMetaError, "Fatal error -- pod_types in table are not same as pod_types in class table . . ."
304            
305            # All the things that could change: 

306            #

307            #    What could happen:

308            #         1. Drop:                     the new_pod_types might not have a pod_type found in old_pod_types -->  In this case, drop the old pod_type. 

309            #         2. Add:                      the new_pod_types might have a pod_type not found in old_pod_types -->  In this case, add the new pod_type. 

310            #         3. Change typed.Typed:       a new_pod_type might have changed type -- in this case, drop and add. 

311            #         4. Change Index or Unique:   the new_pod_types might have a pod_type whose index/unique is not the same as the old pod_type --> In this case, either add/drop index on that pod_type. 

312            #         5. Do nothing!:              a new pod_type and the old pod_type match!!!  YES!!

313            #

314            #    After this, update the mtime on the class and restore pod_type in the database. 

315
316            set_new_pod_types = set(self.typed_group.keys())
317            
318            set_changed_type_pod_types = set([name for name in set_new_pod_types & set_old_pod_types if self.typed_group[name].__class__ != old_pod_types[name].__class__])
319            
320            # You need to drop 1) all pod_types that changed type and 2) if auto_drop is true, you need to also drop set_old_pod_types - set_new_pod_types

321            #

322            # ** auto_drop no longer supported, so this line has changed

323            # set_drop_pod_types = set_changed_type_pod_types if self.auto_drop is False else set_changed_type_pod_types | (set_old_pod_types - set_new_pod_types)

324            set_drop_pod_types = set_changed_type_pod_types | (set_old_pod_types - set_new_pod_types)
325            set_add_pod_types = set_changed_type_pod_types | (set_new_pod_types - set_old_pod_types)
326                       
327            # 1. Drop 

328            if len(set_drop_pod_types) > 0:
329                self.type_drop_old(old_pod_types=old_pod_types, set_old_pod_types=set_old_pod_types, set_drop_pod_types=set_drop_pod_types, copy_dropped_to_dict=copy_dropped_to_dict)
330
331            # 2. Add

332            for name in set_add_pod_types:
333                self.type_create(name=name, pod_type=self.typed_group[name], is_new=False)
334
335            # 4. Change index

336            for new_pod_type, old_pod_type in [(self.typed_group[name], old_pod_types[name]) for name in (set_old_pod_types & set_new_pod_types)]:   
337                if new_pod_type.index != old_pod_type.index:
338                    if old_pod_type.index:
339                        self.type_drop_index(name=old_pod_type.name)
340                    if new_pod_type.index:
341                        self.type_add_index(name=new_pod_type.name) #, unique=new_pod_type.index == typed.unique)

342                    
343            self.cursor.table_state_set()
344
345        def table_change_msg(self, msg=None):
346            
347            if self.db.chatty:
348                if 'start_message' not in self.__dict__:
349                    self.start_message = "Class '" + self.cls.__name__ + "' needs to be created/updated . . . checking to see if any pod_types need to be updated:"
350                if self.start_message:
351                    print self.start_message
352                    self.start_message = False
353                if msg:
354                    print "\tFor class '" + self.cls.__name__ + "' " + msg
355        
356        """ TYPE OPERATIONS """ 
357        def type_drop_old(self, old_pod_types, set_old_pod_types, set_drop_pod_types, copy_dropped_to_dict):
358            
359            set_keep_pod_types = set_old_pod_types - set_drop_pod_types
360            self.table_change_msg(msg="dropping pod_types " + ",".join(set_drop_pod_types) + "  . . . ")
361            list_keep_pod_types = list(set_keep_pod_types)
362            list_drop_pod_types = list(set_drop_pod_types)
363
364            if copy_dropped_to_dict and len(list_drop_pod_types) > 0:
365                self.cursor.execute("SELECT " + ",".join(['id'] + list_drop_pod_types) + " FROM " + self.table)
366                args = []
367                cache = self.db.cache
368                for row in self.cursor.fetchall():
369                    for i, type_name in enumerate(list_drop_pod_types):
370                        old_pod_type = old_pod_types[type_name]
371                        value = row[i+1]
372                        if value is None:
373                            new_value = None
374                        elif isinstance(old_pod_type, typed.Object):
375                            new_value = value
376                        elif isinstance(old_pod_type, typed.PodObject):
377                            new_value = Undefined(id = value.split(":"), cache = cache)
378                        else:
379                            new_value = old_pod_type.load(value)                             
380                        args.append((row[0], type_name,self.cursor.pickle_dump(value = new_value),))  # Don't need to provide inst,attr for mutables check because this is a copy

381                self.cursor.executemany("INSERT OR REPLACE INTO " + self.table_dict + " (fid,key,value) VALUES (?,?,?)", args)
382
383            # Now, put humpty dumpty back together again . . . 

384            self.cursor.execute("SELECT " + ",".join(['id'] + list_keep_pod_types) + " FROM " + self.table)
385            rows = self.cursor.fetchall()
386                        
387            self.cursor.execute("DROP TABLE IF EXISTS " + self.table)                
388            self.cursor.execute("CREATE TABLE IF NOT EXISTS " + self.table + " (id INTEGER PRIMARY KEY)")
389            
390            for name in list_keep_pod_types:
391                old_pod_types[name].on_pod_init(cls_pod=self, name=name)
392                self.type_create(name=name, pod_type=old_pod_types[name], is_new=True)
393            
394            # NOW PUT ALL THE ROWS BACK INTO DB -- HOWEVER, COPY PED COLUMN DATA BACK INTO THE DICT

395
396            names = '(' + ",".join(['id'] + list_keep_pod_types) + ')'                
397            quest = '(' + ",".join(["?" for i in range(len(list_keep_pod_types) + 1)]) + ')'
398            self.cursor.executemany("INSERT INTO " + self.table + " " + names + " VALUES " + quest, (row[0:len(list_keep_pod_types) + 1] for row in rows))
399                
400        def type_drop_and_delete_forever(self, pod_type):
401
402            if self.db.chatty:  
403                print "You have chosen to permentally drop and delete pod_type '" + pod_type.name + "' from '" + self.table + "'.  All data will be lost forever "
404                print "Remember!  You have to remove this pod.typed attribute from the class header or else it will be added back on next import . . . "
405            
406            delattr(self.cls, pod_type.name)
407            del self.typed_group[pod_type.name]
408            
409            self.table_check_for_changes(index_kv_old = self.index_kv, copy_dropped_to_dict=False)
410            
411            for child_pod in self.child_pods:
412                child_pod.type_drop_and_delete_forever(child_pod.typed_group[pod_type.name])
413
414            # Now you want to resave a local mtime, because we want to reimport the class after this . . . 

415            self.cursor.table_state_set()
416            
417            for inst in self.cache.itervalues():
418                dict = object.__getattribute__(inst, '__dict__')
419                if pod_type.name in dict:
420                    del dict[pod_type.name]
421            
422        def type_create(self, name, pod_type, is_new):
423            # If the pod_type is new, then you don't need to copy all the data from the __dict__ to the new pod_type. 

424                    
425            self.table_change_msg(msg="adding pod_type '" + name + "'  . . . ")
426            self.cursor.execute("ALTER TABLE " + self.table + " ADD COLUMN " + pod_type.get_alter_table())
427    
428            if pod_type.index:
429                self.type_add_index(name=name)
430                
431            if is_new is False:
432                # IF NOT NEW, THEN, ADD COLUMN TO DATABASE

433                self.cursor.execute("SELECT fid,value FROM " + self.table_dict + " WHERE key=?", (name,))                          
434                # NOW, COPY THE NEW VALUE FROM THE inst.__dict__ TO THE COLUMN . . . 

435                # Just note, on pod_type.dump call, you only need to provide 'value' since this is a copy and the db.mutables

436                # stack will not need updating . . . 

437                self.cursor.executemany("UPDATE " + self.table + " SET " + name + "=? WHERE id=?", [(pod_type.dump(value = self.cursor.pickle_load(row[1])), row[0]) for row in self.cursor.fetchall()])
438                self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE key=?", (name,))
439                                    
440            # And finally, store new pod_types to database 

441            self.cursor.table_state_set()
442     
443        def type_add_index(self, name, unique=False):
444            unique = "" if unique is False else "UNIQUE "
445            self.table_change_msg(msg="adding " + unique.lower() + "index '" + name + "'  . . . ")
446            self.cursor.execute("CREATE " + unique + "INDEX IF NOT EXISTS " + self.table + "_pin_" + name + " ON " + self.table + " (" + name + ")")
447                    
448        def type_drop_index(self, name):        
449            self.table_change_msg(msg="dropping index '" + name + "'  . . . ")
450            self.cursor.execute("DROP INDEX IF EXISTS " + self.table + "_pin_" + name)
451                   
452        def type_get_current_db_pod_types(self):            
453            self.cursor.execute("PRAGMA table_info(" + self.table + ")")
454            return [str(col[1]) for col in self.cursor.fetchall() if str(col[1]) != 'id']
455        
456        def type_get_current_table_indexes(self):
457            self.cursor.execute("PRAGMA index_list(" + self.table + ")")
458            return [str(col[1]).replace(self.table + "_pin_", "") for col in self.cursor.fetchall()]
459
460        def type_get_current_table_dict_indexes(self):
461            self.cursor.execute("PRAGMA index_list(" + self.table_dict + ")")
462            return [str(col[1]).replace(self.table + "_pin_", "") for col in self.cursor.fetchall()]
463                                                   
464        """ instance methods """
465        def inst_new(self, inst=None, kwargs=None):
466                
467            # This is for special pod_type types (like pod_type.TimeCreate) which want to do something on create . . 

468            for pod_type in set(self.type_callbacks_create) | set(self.type_callbacks_load):
469                inst, kwargs = pod_type.on_inst_create(inst=inst, kwargs=kwargs)
470                   
471            typed_values    = {}
472            dynamic_values = {}
473           
474            if kwargs is not None:
475                object.__getattribute__(inst, '__dict__').update(kwargs)  # This sets the objects dict

476                for key, value in kwargs.iteritems():
477                    if key in self.typed_group:
478                        typed_values[key] = self.typed_group[key].dump(value = value, inst = inst)
479                    else:
480                        dynamic_values[key] = self.cursor.pickle_dump(value = value, inst = inst, attr = key)
481    
482            inst_id = self.cursor.insert_object(values = typed_values)
483            self.cursor.add_many_kvargs(values = dynamic_values, inst_id = inst_id)
484     
485            object.__setattr__(inst, 'id', inst_id)                                  
486            self.cache[inst_id] = inst
487                                        
488            object.__getattribute__(inst, 'on_new_or_load_from_db')()
489            
490            #Then, add it to the list of 'fully loaded' instances 

491            self.fulls.add(inst)
492            
493            return inst
494                
495        def inst_update_dict(self, inst, kwargs):
496            
497            object.__getattribute__(inst, '__dict__').update(kwargs)
498            
499            type_attrs = ""
500            type_values = []
501            nom_values = []
502            id = inst.id         
503               
504            for key, value in kwargs.items():
505                self.__setattr__(key, value)
506                if key in self.typed_group:
507                    type_attrs += key + '=?,'
508                    type_values.append(self.typed_group[key].dump(value = value, inst = inst))
509                else:
510                    nom_values.append((id, key,self.cursor.pickle_dump(value = value, inst = inst, attr = key),))
511
512            if len(type_values) > 0:
513                type_values.append(id)
514                self.cursor.execute('UPDATE ' + self.table + ' SET ' + type_attrs[:-1] + ' WHERE id=?', type_values)
515 
516            if len(nom_values) > 0:
517                self.cursor.executemany('INSERT OR REPLACE INTO ' + self.table_dict + ' (fid,key,value) VALUES (?,?,?)', nom_values)                    
518                      
519        def inst_get_inst_by_id(self, inst_id, zombie=True):
520            
521            if inst_id in self.cache:
522                return self.cache[inst_id]                
523            else:
524                inst = self.cls.__new__(self.cls)
525                if zombie:
526                    self.zombies.add(inst)
527                object.__setattr__(inst, 'id', inst_id)
528                object.__getattribute__(inst, 'on_new_or_load_from_db')()
529                object.__getattribute__(inst, 'on_load_from_db')()
530                self.cache[inst_id] = inst
531                for pod_type in self.type_callbacks_load:
532                    inst = pod_type.on_inst_load(inst=inst)
533                return inst
534        
535        def inst_load_attr_from_db(self, inst, attr):
536            
537            if attr in self.typed_group:
538                self.cursor.execute("SELECT " + attr + " FROM " + self.table + " WHERE id=?", (object.__getattribute__(inst, 'id'),))
539                row = self.cursor.fetchone()
540                if row:
541                    self.zombies.discard(inst)
542                    object.__setattr__(inst, attr, self.typed_group[attr].load(row[0]))                    
543                else:
544                    raise PodObjectDeleted, "The object " + str(inst) + " with id " + inst.get_full_id() + " had been deleted . .  ."
545            else:
546                self.cursor.execute("SELECT value FROM " + self.table_dict + " WHERE fid=? AND key=?", (object.__getattribute__(inst, 'id'), attr,))
547                row = self.cursor.fetchone()
548                if row:
549                    self.zombies.discard(inst)
550                    object.__setattr__(inst, attr, self.cursor.pickle_load(value = row[0], inst = inst, attr = attr))
551                else:
552                    self.inst_check_if_exists(inst)
553
554        def inst_set_attr(self, inst, attr, value):
555            if attr in Meta.POD_SETI_SET:
556                raise PodObjectError, "You cannot set attr '" + attr + "' of a pod object . . ."
557            if self.inst_check_if_exists(inst):
558                object.__setattr__(inst, attr, value)         
559                self.inst_save_attr_to_db(inst, attr, value)
560        
561        def inst_save_attr_to_db(self, inst, attr, value):
562            if attr in self.typed_group:
563                self.cursor.execute('UPDATE ' + self.table + ' SET ' + attr + '=? WHERE id=?', (self.typed_group[attr].dump(value = value, inst = inst), inst.id,))
564            else:
565                self.cursor.execute('INSERT OR REPLACE INTO ' + self.table_dict + ' (fid,key,value) VALUES (?,?,?)', (inst.id, attr, self.cursor.pickle_dump(value = value, inst = inst, attr = attr),))                    
566                
567        def inst_del_attr(self, inst, attr):
568            # If the attribute is in inst's pod_types, you can't really delete it -- just set it to None (since the pod_type will have NULL)

569            # If not in pod_type group, then delete it from the table. 

570            
571            id = object.__getattribute__(inst, 'id')
572                        
573            if attr in self.typed_group:
574                self.cursor.execute('UPDATE ' + self.table + ' SET ' + attr + '=? WHERE id=?', (None,id,))
575                object.__setattr__(inst, attr, None)
576            else:
577                self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE fid=? AND key=?", (id, attr,))  
578                dict = object.__getattribute__(inst, '__dict__')
579                if attr in dict:
580                    del dict[attr]
581                                            
582        def inst_delete(self, inst):
583            object.__getattribute__(inst, 'pre_delete')()
584            id = object.__getattribute__(inst, 'id')
585            self.cursor.execute("DELETE FROM " + self.table + " WHERE id=?", (id,))
586            self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE fid=?", (id,))
587            if inst in self.db.mutables:
588                del self.db.mutables[inst]
589            # THEN, MAKE IT A DELETED OBJECT()

590            object.__setattr__(inst, '__class__', Deleted)
591            object.__setattr__(inst, '__dict__', {})
592            return None
593
594        def inst_full_load(self, inst):                    
595            if inst not in self.fulls:
596                cursor    = self.cursor
597                id        = object.__getattribute__(inst, 'id')
598                inst_dict = object.__getattribute__(inst, '__dict__')
599                pod_types = [pod_type for pod_type in self.typed_group.keys() if pod_type not in inst_dict]
600                cursor.execute("SELECT " + ",".join(['id'] + pod_types) + " FROM " + self.table + " WHERE id=?", (id,))
601                row = cursor.fetchone()
602                if row:
603                    self.zombies.discard(inst)
604                    self.fulls.add(inst)
605                    for i, name in enumerate(pod_types):
606                        object.__setattr__(inst, name, self.typed_group[name].load(row[i+1]))
607                    cursor.execute("SELECT key,value FROM " + self.table_dict + " WHERE fid=?", (id,))
608                    rows = cursor.fetchall()
609                    for kv in rows:
610                        if kv[0] not in inst_dict:
611                            object.__setattr__(inst, kv[0], self.cursor.pickle_load(value = kv[1], inst = inst, attr = kv[0]))               
612                else:
613                    raise PodObjectDeleted, "The object " + str(inst) + " with id " + inst.get_full_id() + " had been deleted . .  ."
614          
615        def inst_clear_all(self, inst):
616            id = object.__getattribute__(inst, 'id')
617            sets = ""
618            args = []
619            new_dict = {'id': id}
620            for type in self.typed_group.keys():
621                sets += type + '=?,'
622                args.append(None)
623                new_dict[type] = None
624                    
625            if len(args) > 0:
626                args.append(id)
627                self.cursor.execute('UPDATE ' + self.table + ' SET ' + sets[:-1] + ' WHERE id=?', args)
628            
629            self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE fid=?", (id,))
630            
631            object.__setattr__(inst, '__dict__', new_dict)
632            
633        def inst_len(self, inst, id = False):
634            if inst in self.fulls:
635                return len(object.__getattribute__(inst,'__dict__')) - (0 if id else 1)
636            else:
637                return len(self.typed_group) + self.cursor.execute('SELECT COUNT(rowid) FROM ' + self.table_dict + ' WHERE fid=?', (object.__getattribute__(inst,'id'),)).fetchone()[0] + (1 if id else 0)
638            
639        def inst_contains(self, inst, key):
640            if inst in self.fulls:
641                return key in object.__getattribute__(inst,'__dict__')
642            else:
643                if key in self.typed_group:
644                    return True
645                else:
646                    return bool(self.cursor.execute('SELECT COUNT(rowid) FROM ' + self.table_dict + ' WHERE fid=? AND key=?', (object.__getattribute__(inst,'id'),key,)).fetchone()[0])
647                    
648        def inst_check_if_exists(self, inst, error = True):
649            if inst not in self.zombies:
650                return True
651            else:
652                self.cursor.execute("SELECT id FROM " + self.table + " WHERE id=?", (object.__getattribute__(inst, 'id'),))
653                row = self.cursor.fetchone()
654                if row:
655                    self.zombies.discard(inst)
656                elif error:
657                    raise PodObjectDeleted, "The object " + str(inst) + " with id " + inst.get_full_id() + " has been deleted . .  ."
658
659    class PodNoDatabase(object):
660        
661        def __getattribute__(self, key):
662            if key == 'attach_to_db':
663                object.__setattr__(self, '__class__', type.__getattribute__(object.__getattribute__(self, 'cls'), 'Pod'))
664                return object.__getattribute__(self, key)
665            elif key in ['id', 'child_pods']:
666                return object.__getattribute__(self, key)
667            else:
668                raise PodNoDatabaseError, "You have not connected the class '" + str(object.__getattribute__(self, 'cls')) + "' to any database . . . "
669            
670        def __setattr__(self, key, value):
671            raise PodNoDatabaseError, "You have not connected the class '" + str(object.__getattribute__(self, 'cls')) + "' to any database . . . "
672        
673    class Where(object):
674        
675        def __init__(self, cls_pod):
676            self.cls_pod = cls_pod
677        
678        def __getattr__(self, key):
679            if key in self.cls_pod.typed_group:
680                return self.cls_pod.typed_group[key]
681            else:
682                return typed.Dynamic(cls_pod=self.cls_pod, name=key)  
683              
684""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
685##

686##   Object: THE OBJECT IN P'O'D

687##

688""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
689class PodObjectError(exceptions.BaseException):
690    pass
691
692class PodObjectDeleted(exceptions.BaseException):
693    pass
694
695class PodObjectUndefined(exceptions.BaseException):
696    pass
697
698class Object(object):
699
700    __metaclass__ = Meta
701    
702    def __init__(self, **kwargs):
703        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_new(inst=self, kwargs=kwargs)
704
705    def __getattribute__(self, attr):  
706        
707        dict = object.__getattribute__(self, '__dict__')
708        
709        if attr in dict:
710            value = dict[attr]
711        elif attr == '__dict__':
712            return dict
713        else:
714            cls_pod = type.__getattribute__(object.__getattribute__(self, '__class__'), 'pod')            
715            if attr in cls_pod.typed_group:
716                cls_pod.inst_load_attr_from_db(self, attr)
717                value = dict[attr]
718            else:
719                return object.__getattribute__(self, attr)  # This, then, will call __getattr__ on fail and try to load from database . . . '

720                    
721        return value
722                  
723    def __getattr__(self, attr):
724        dict = object.__getattribute__(self, '__dict__')
725        cls_pod = object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod')            
726        cls_pod.inst_load_attr_from_db(self, attr)
727        if attr in dict:
728            return dict[attr]
729        else:
730            raise AttributeError, "'" + self.__class__.__name__ + "' object has no attribute '" + attr + "'"
731                            
732    def __setattr__(self, attr, value): 
733        return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_set_attr(self, attr, value)
734    
735    def __delattr__(self, attr):
736        return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_del_attr(self, attr)
737        
738    def delete(self):
739        return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_delete(self)
740    
741    def full_load(self):
742        return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
743        
744    def get_full_id(self):
745        return str(type.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').id) + ":" + str(object.__getattribute__(self, 'id')) 
746
747    def set_many(self, **kwargs):
748        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_update_dict(self, kwargs)
749
750    def update(self, other):
751        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_update_dict(self, other)
752
753    """ pass through convenience functions """
754    def on_new_or_load_from_db(self):
755        pass
756
757    def on_load_from_db(self):
758        pass
759    
760    def pre_delete(self):
761        pass
762              
763    """ dictionary interface """
764    def __len__(self, id = False):
765        return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_len(self, id = False)
766
767    def __getitem__(self, key):
768        return getattr(self, key)
769    
770    def __setitem__(self, key, value):
771        return object.__getattribute__(self, '__setattr__')(key, value)
772
773    def __contains__(self, key):
774        return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_contains(inst = self, key = key)
775
776    def __iter__(self):
777        return self.iterkeys(id = False)
778    
779    def clear(self):
780        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_clear_all(self)
781         
782    def copy(self, id = False):
783        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
784        new_dict = object.__getattribute__(self, '__dict__').copy()
785        if id is False:
786            del new_dict['id']
787        return new_dict
788
789    def get(self, key, default = None):
790        return getattr(self, key, default)
791    
792    def setdefault(self, key, default):
793        try:
794            return getattr(self, key)
795        except AttributeError:
796            object.__getattribute__(self, '__setattr__')(key, default)
797            return default
798        
799    def keys(self, id = False):
800        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
801        return [key for key in object.__getattribute__(self, '__dict__').iterkeys() if key != 'id' or id]
802    
803    def iterkeys(self, id = False):
804        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
805        return (key for key in object.__getattribute__(self, '__dict__').iterkeys() if key != 'id' or id)
806
807    def values(self, id = False):
808        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
809        return [value for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id]
810    
811    def itervalues(self, id = False):
812        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
813        return (value for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id)
814    
815    def items(self, id = False):
816        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
817        return [(key,value) for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id]
818    
819    def iteritems(self, id = False):
820        object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
821        return ((key,value) for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id)
822               
823class Undefined(Object):
824    
825    POD_GETA_SET = set(['__class__', '__init__', '__reduce__', '__reduce_ex__', 'get_full_id'])
826    
827    def __init__(self, id, cache):
828        object.__setattr__(self, 'id',    id)
829        object.__setattr__(self, 'cache', cache)
830        
831    def get_full_id(self):
832        id = object.__getattribute__(self, 'id')
833        return str(id[0]) + ":" + str(id[1])
834
835    def __getattribute__(self, key):
836        if key in Undefined.POD_GETA_SET:
837            return object.__getattribute__(self, key)
838        else:
839            object.__getattribute__(self, 'try_to_activate_class')(key)
840            # The above function changes the class, preventing an infinite loop

841            return getattr(self, key)
842
843    def __setattr__(self, key, value):
844        object.__getattribute__(self, 'try_to_activate_class')(key)
845        self.__setattr__(key, value)
846    
847    def try_to_activate_class(self, key):
848        id    = object.__getattribute__(self, 'id')
849        cache = object.__getattribute__(self, 'cache')        
850        cls_id, inst_id = id[0], id[1]
851        if cls_id in cache.class_pods:
852            cls_pod = cache.class_pods[cls_id]
853            inst_id = id[1]
854            object.__setattr__(self, '__class__', cls_pod.cls)
855            object.__setattr__(self, 'id', inst_id)
856            object.__delattr__(self, 'cache')
857            cls_pod.cache[inst_id] = self
858        else:
859            raise PodObjectUndefined, "You tried to access attr '" + key + "' on a pod.Undefined of class type -- this means the class was not loaded at import time. . . "
860        
861""" Things you don't want to save """
862class NoSave(object):
863    
864    def get_full_id(self):
865        return "0:0"
866  
867class Deleted(NoSave):
868    
869    
870    def get_full_id(self):
871        return "0:0"
872
873    def __getattribute__(self, key):
874        raise PodObjectDeleted, "You tried to get attr '" + key + "' on a pod.Deleted object -- this means that this is a reference to an object that was deleted . . . "
875    
876    def __setattr__(self, key, value):
877        raise PodObjectDeleted, "You tried to set attr '" + key + "' on a pod.Deleted object -- this means that this is a reference to an object that was deleted . . . "
878
879