/pod/core.py

http://pickled-object-database.googlecode.com/ · Python · 879 lines · 671 code · 147 blank · 61 comment · 161 complexity · 1e4c5fa9f0b1da70c445ba1691086df8 MD5 · raw file

  1. import os
  2. import sys
  3. import time
  4. import exceptions
  5. import warnings
  6. import inspect
  7. import db
  8. import typed
  9. from query import Query, RawQuery
  10. import fn
  11. class PodMetaError(exceptions.BaseException):
  12. pass
  13. class PodNoDatabaseError(exceptions.BaseException):
  14. pass
  15. class Meta(type):
  16. POD_RESV_SET = set(['id', 'pod', 'store', 'where'])
  17. POD_GETA_SET = set(['__new__', '__instancecheck__', 'pod', 'where', '__class__', '__dict__'])
  18. POD_SETI_SET = set(['__class__', 'id'])
  19. def __init__(cls, name, bases, dict):
  20. if cls.__module__ == Meta.__module__:
  21. # We don't want to create tables and so on for the base classes defined in this module.
  22. return
  23. if len(bases) > 1:
  24. raise PodMetaError, "Currently, pod does not support multiple inheritance . . . "
  25. for key in Meta.POD_RESV_SET:
  26. if key in dict:
  27. raise PodMetaError, "'" + key + "' is a reserved class variable and cannot be defined in class " + str(cls)
  28. cls_pod = type(cls).Pod(cls=cls, parent=bases[0] if bases[0] is not Object else None)
  29. type.__setattr__(cls, 'pod', cls_pod)
  30. type.__setattr__(cls, 'where', type(cls).Where(cls_pod = cls_pod))
  31. def __getattribute__(cls, key):
  32. if key in Meta.POD_GETA_SET or key in type.__getattribute__(cls, '__dict__'):
  33. return type.__getattribute__(cls, key)
  34. elif key == 'store':
  35. store = db.Store(db=type.__getattribute__(cls, 'pod').db, prefix=type.__getattribute__(cls, 'pod').table + '_store_')
  36. type.__setattr__(cls, 'store', store)
  37. return store
  38. elif key == 'id':
  39. id = typed.Int(index=False, cls_pod=type.__getattribute__(cls, 'pod'), name='id')
  40. type.__setattr__(cls, 'id', id)
  41. return id
  42. else:
  43. return type.__getattribute__(cls, key)
  44. def __setattr__(cls, key, value):
  45. # Here, __setattr__ is only performing error checking because dynamic creation of pod_types has been removed . . .
  46. if key in Meta.POD_RESV_SET:
  47. raise PodMetaError, "Key '" + key + "' is a reserved attribute of pod classes . . . "
  48. if isinstance(value, typed.Typed):
  49. raise PodMetaError, "You cannot create a pod_type '" + key + "' dynamically -- please add it to the class header . . . "
  50. if isinstance(getattr(cls, key, None), typed.Typed):
  51. raise PodMetaError, "Attr '" + key + "' is of type pod.typed.Typed -- you cannot alter this attribute dynamically -- it must be performed in class header . . ."
  52. return type.__setattr__(cls, key, value)
  53. def __iter__(cls):
  54. return Query(where=cls).__iter__()
  55. def execute(cls, query, args=()):
  56. return cls.pod.cursor.execute(query.replace("cls_table ", cls.pod.table + " ").replace("cls_table_dict", cls.pod.table_dict), args)
  57. def executemany(cls, query, args=()):
  58. return cls.pod.cursor.executemany(query.replace("cls_table ", cls.pod.table + " ").replace("cls_table_dict", cls.pod.table_dict), args)
  59. def migrate(cls, new_cls):
  60. if not isinstance(new_cls, Meta):
  61. raise PodMetaError, "New class must be of type pod.Object"
  62. for obj in Query(where=cls, child_classes = False):
  63. obj.full_load()
  64. new_cls(**obj.copy())
  65. cls.drop(child_classes = False)
  66. def drop(cls, child_classes = True):
  67. cls.pod.table_drop(child_classes = child_classes)
  68. def clear_all(cls, child_classes = True):
  69. cls.pod.table_clear(child_classes = child_classes)
  70. def get_count(cls, child_classes=True, count=0):
  71. get_one = RawQuery(select=fn.count(cls.id) >> 'count').get_one()
  72. count = get_one.count + count if get_one else count
  73. if child_classes:
  74. for child_pod in cls.pod.child_pods:
  75. count = child_pod.cls.get_count(child_classes=child_classes, count=count)
  76. return count
  77. def get_db(cls):
  78. return type.__getattribute__(cls, 'pod').db
  79. class Pod(object):
  80. def __init__(self, cls, parent):
  81. # pass ins
  82. self.cls = cls
  83. self.parent_pod = getattr(parent, 'pod', None)
  84. self.db = None
  85. self.id = None
  86. self.table = self.get_table_name()
  87. # Init vars
  88. self.typed_group = {}
  89. self.type_callbacks_create = set()
  90. self.type_callbacks_load = set()
  91. self.child_pods = set()
  92. # First, update parent and add any pod_types a parent has to your local dictionary.
  93. # 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'
  94. # you want ParentClass.name == 'Fred' to return all objects with a name of 'Fred' but ChildClass.name == 'Fred' to
  95. # only return ChildClass object with a name == 'Fred'. type_add_parents takes care of this . . .
  96. # This updates all the pod_types passed in from the class header.
  97. for name, pod_type in self.cls.__dict__.iteritems():
  98. if isinstance(pod_type, typed.Typed):
  99. self.typed_group[name] = pod_type
  100. pod_type.on_pod_init(cls_pod=self, name=name)
  101. if self.parent_pod is not None:
  102. object.__getattribute__(self.parent_pod, 'child_pods').add(self)
  103. for name, pod_type in object.__getattribute__(self.parent_pod, 'typed_group').iteritems():
  104. if name in self.cls.__dict__:
  105. 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')) + " . . ."
  106. else:
  107. pod_type = pod_type.get_deep_copy(cls_pod=self, name=name)
  108. type.__setattr__(self.cls, name, pod_type)
  109. self.typed_group[name] = pod_type
  110. # register table name
  111. if db.register.global_db is None: # This means no db connection has been made yet
  112. db.register.pods_to_attach[self.table] = self
  113. self.__class__ = type.__getattribute__(cls, 'PodNoDatabase')
  114. elif db.register.global_db is False: # False means db connection was made with 'attach' parameter
  115. self.db = self.get_parent_db()
  116. elif db.register.global_db:
  117. self.attach_to_db(db = db.register.global_db)
  118. def __getstate__(self):
  119. raise PodMetaError, "You cannot pickle a Pod CLASS pod._Meta object . . . "
  120. def get_parent_db(self):
  121. if self.parent_pod:
  122. if self.parent_pod.db:
  123. db = self.parent_pod.db if self.parent_pod.db is not None else self.parent_pod.get_parent_db()
  124. self.attach_to_db(db = db)
  125. return db
  126. else:
  127. return None
  128. def attach_to_db(self, db):
  129. # If the local 'self.db' is not set, set it to the current and update all your children.
  130. # If it is already set, that's okay as long as the two databases match.
  131. if self.db is None:
  132. self.table_dict = self.table + '_kvdict'
  133. self.db = db
  134. self.cursor = db.get_new_cursor(cls_pod = self)
  135. table_state = self.cursor.table_state_get()
  136. if table_state is None:
  137. self.table_create() # Note, this calls 'activate_on_get_table_id' at the right place . . .
  138. else:
  139. self.id = table_state['id']
  140. index_kv_old = table_state['index_kv']
  141. self.index_kv = getattr(self.cls, 'POD_DYNAMIC_INDEX', self.db.dynamic_index)
  142. self.mtime = self.get_mtime(source_file=inspect.getsourcefile(self.cls))
  143. self.activate_on_get_table_id()
  144. if (int(table_state['mtime']) != self.mtime) or (self.index_kv != index_kv_old):
  145. self.table_check_for_changes(index_kv_old = index_kv_old)
  146. for child_pod in self.child_pods:
  147. child_pod.attach_to_db(db = db)
  148. elif db is not self.db:
  149. raise PodMetaError, "Class '" + str(self.cls) + "' is already attached to a database"
  150. def activate_on_get_table_id(self):
  151. """ This functions is only called by 1) activate or 2) table_create """
  152. # This is used by both activate and table_create
  153. self.db.cache.class_pods[self.id] = self
  154. self.cache = {}
  155. self.zombies = set()
  156. self.fulls = set()
  157. for pod_type in self.typed_group.itervalues():
  158. pod_type.on_db_attach(db=self.db, cls_pod = self)
  159. def clear(self):
  160. self.cache.clear()
  161. self.zombies.clear()
  162. self.fulls.clear()
  163. def get_table_name(self):
  164. #
  165. # The table name is:
  166. # 1. The module path plus the class name, for example myapp_models_Person.
  167. # 2. Just the class name (e.g. just Person) if the user has set POD_TABLE_NAME set to True
  168. # in class header.
  169. # 3. POD_TABLE_NAME if it's set to a string in the class header
  170. #
  171. # Full name is harder to read if working with sql table directly, but
  172. # uses full module name so it is less likely to have a namespace collision.
  173. #
  174. cls_name = type.__getattribute__(self.cls, '__name__')
  175. pod_table_name = type.__getattribute__(self.cls, '__dict__').get('POD_TABLE_NAME', None)
  176. if pod_table_name is None:
  177. return '_'.join(self.cls.__module__.split('.') + [cls_name])
  178. elif pod_table_name is True:
  179. return cls_name
  180. else:
  181. return pod_table_name
  182. def get_mtime(self, source_file=None):
  183. try:
  184. return int(os.path.getmtime(source_file)) if source_file else int(time.time())
  185. except:
  186. if source_file[-7:] == 'list.py' or source_file[-7:] == 'dict.py' or source_file[-6:] == 'set.py':
  187. return 0
  188. else:
  189. raise PodMetaError, "Cannot get mtime on file '" + source_file + "' . . ."
  190. """ TABLE OPERATIONS """
  191. def table_create(self):
  192. # First, get the variables . . .
  193. self.mtime = self.get_mtime(source_file=inspect.getsourcefile(self.cls))
  194. self.index_kv = type.__getattribute__(self.cls, '__dict__').get('POD_DYNAMIC_INDEX', self.db.dynamic_index)
  195. self.id = self.cursor.create_class_tables()
  196. if self.index_kv:
  197. self.cursor.index_kv_add()
  198. self.activate_on_get_table_id()
  199. for name, pod_type in self.typed_group.iteritems():
  200. # This will call 'sql_table_state_set'
  201. self.type_create(name=name, pod_type=pod_type, is_new=True)
  202. if len(self.typed_group) == 0:
  203. # if len == 0, the iteration above will not call 'sql_table_state_set', so call it.
  204. self.cursor.table_state_set()
  205. def table_drop(self, child_classes = True):
  206. if self.id:
  207. self.cursor.drop_class_tables()
  208. self.db.commit(clear_cache=True, close=False)
  209. if child_classes:
  210. for child_pod in self.child_pods:
  211. child_pod.table_drop()
  212. def table_clear(self, child_classes=True):
  213. if self.id:
  214. self.clear()
  215. self.cursor.clear_class_tables()
  216. if child_classes:
  217. for child_pod in self.child_pods:
  218. child_pod.table_clear()
  219. def table_check_for_changes(self, index_kv_old, copy_dropped_to_dict=True):
  220. if self.index_kv is True and index_kv_old is False:
  221. self.cursor.index_kv_add()
  222. self.table_change_msg(msg='Adding index for dynamic attributes . . . ')
  223. elif self.index_kv is False and index_kv_old is True:
  224. self.cursor.index_kv_drop()
  225. self.table_change_msg(msg='Dropping index for dynamic attributes . . . ')
  226. old_pod_types = self.cursor.get_old_pod_types()
  227. set_old_pod_types = set(old_pod_types.keys())
  228. for name, pod_type in old_pod_types.iteritems():
  229. pod_type.on_pod_init(cls_pod=self, name=name)
  230. if set(self.type_get_current_db_pod_types()) != set_old_pod_types:
  231. raise PodMetaError, "Fatal error -- pod_types in table are not same as pod_types in class table . . ."
  232. # All the things that could change:
  233. #
  234. # What could happen:
  235. # 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.
  236. # 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.
  237. # 3. Change typed.Typed: a new_pod_type might have changed type -- in this case, drop and add.
  238. # 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.
  239. # 5. Do nothing!: a new pod_type and the old pod_type match!!! YES!!
  240. #
  241. # After this, update the mtime on the class and restore pod_type in the database.
  242. set_new_pod_types = set(self.typed_group.keys())
  243. 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__])
  244. # 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
  245. #
  246. # ** auto_drop no longer supported, so this line has changed
  247. # 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)
  248. set_drop_pod_types = set_changed_type_pod_types | (set_old_pod_types - set_new_pod_types)
  249. set_add_pod_types = set_changed_type_pod_types | (set_new_pod_types - set_old_pod_types)
  250. # 1. Drop
  251. if len(set_drop_pod_types) > 0:
  252. 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)
  253. # 2. Add
  254. for name in set_add_pod_types:
  255. self.type_create(name=name, pod_type=self.typed_group[name], is_new=False)
  256. # 4. Change index
  257. 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)]:
  258. if new_pod_type.index != old_pod_type.index:
  259. if old_pod_type.index:
  260. self.type_drop_index(name=old_pod_type.name)
  261. if new_pod_type.index:
  262. self.type_add_index(name=new_pod_type.name) #, unique=new_pod_type.index == typed.unique)
  263. self.cursor.table_state_set()
  264. def table_change_msg(self, msg=None):
  265. if self.db.chatty:
  266. if 'start_message' not in self.__dict__:
  267. self.start_message = "Class '" + self.cls.__name__ + "' needs to be created/updated . . . checking to see if any pod_types need to be updated:"
  268. if self.start_message:
  269. print self.start_message
  270. self.start_message = False
  271. if msg:
  272. print "\tFor class '" + self.cls.__name__ + "' " + msg
  273. """ TYPE OPERATIONS """
  274. def type_drop_old(self, old_pod_types, set_old_pod_types, set_drop_pod_types, copy_dropped_to_dict):
  275. set_keep_pod_types = set_old_pod_types - set_drop_pod_types
  276. self.table_change_msg(msg="dropping pod_types " + ",".join(set_drop_pod_types) + " . . . ")
  277. list_keep_pod_types = list(set_keep_pod_types)
  278. list_drop_pod_types = list(set_drop_pod_types)
  279. if copy_dropped_to_dict and len(list_drop_pod_types) > 0:
  280. self.cursor.execute("SELECT " + ",".join(['id'] + list_drop_pod_types) + " FROM " + self.table)
  281. args = []
  282. cache = self.db.cache
  283. for row in self.cursor.fetchall():
  284. for i, type_name in enumerate(list_drop_pod_types):
  285. old_pod_type = old_pod_types[type_name]
  286. value = row[i+1]
  287. if value is None:
  288. new_value = None
  289. elif isinstance(old_pod_type, typed.Object):
  290. new_value = value
  291. elif isinstance(old_pod_type, typed.PodObject):
  292. new_value = Undefined(id = value.split(":"), cache = cache)
  293. else:
  294. new_value = old_pod_type.load(value)
  295. 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
  296. self.cursor.executemany("INSERT OR REPLACE INTO " + self.table_dict + " (fid,key,value) VALUES (?,?,?)", args)
  297. # Now, put humpty dumpty back together again . . .
  298. self.cursor.execute("SELECT " + ",".join(['id'] + list_keep_pod_types) + " FROM " + self.table)
  299. rows = self.cursor.fetchall()
  300. self.cursor.execute("DROP TABLE IF EXISTS " + self.table)
  301. self.cursor.execute("CREATE TABLE IF NOT EXISTS " + self.table + " (id INTEGER PRIMARY KEY)")
  302. for name in list_keep_pod_types:
  303. old_pod_types[name].on_pod_init(cls_pod=self, name=name)
  304. self.type_create(name=name, pod_type=old_pod_types[name], is_new=True)
  305. # NOW PUT ALL THE ROWS BACK INTO DB -- HOWEVER, COPY PED COLUMN DATA BACK INTO THE DICT
  306. names = '(' + ",".join(['id'] + list_keep_pod_types) + ')'
  307. quest = '(' + ",".join(["?" for i in range(len(list_keep_pod_types) + 1)]) + ')'
  308. self.cursor.executemany("INSERT INTO " + self.table + " " + names + " VALUES " + quest, (row[0:len(list_keep_pod_types) + 1] for row in rows))
  309. def type_drop_and_delete_forever(self, pod_type):
  310. if self.db.chatty:
  311. print "You have chosen to permentally drop and delete pod_type '" + pod_type.name + "' from '" + self.table + "'. All data will be lost forever "
  312. print "Remember! You have to remove this pod.typed attribute from the class header or else it will be added back on next import . . . "
  313. delattr(self.cls, pod_type.name)
  314. del self.typed_group[pod_type.name]
  315. self.table_check_for_changes(index_kv_old = self.index_kv, copy_dropped_to_dict=False)
  316. for child_pod in self.child_pods:
  317. child_pod.type_drop_and_delete_forever(child_pod.typed_group[pod_type.name])
  318. # Now you want to resave a local mtime, because we want to reimport the class after this . . .
  319. self.cursor.table_state_set()
  320. for inst in self.cache.itervalues():
  321. dict = object.__getattribute__(inst, '__dict__')
  322. if pod_type.name in dict:
  323. del dict[pod_type.name]
  324. def type_create(self, name, pod_type, is_new):
  325. # If the pod_type is new, then you don't need to copy all the data from the __dict__ to the new pod_type.
  326. self.table_change_msg(msg="adding pod_type '" + name + "' . . . ")
  327. self.cursor.execute("ALTER TABLE " + self.table + " ADD COLUMN " + pod_type.get_alter_table())
  328. if pod_type.index:
  329. self.type_add_index(name=name)
  330. if is_new is False:
  331. # IF NOT NEW, THEN, ADD COLUMN TO DATABASE
  332. self.cursor.execute("SELECT fid,value FROM " + self.table_dict + " WHERE key=?", (name,))
  333. # NOW, COPY THE NEW VALUE FROM THE inst.__dict__ TO THE COLUMN . . .
  334. # Just note, on pod_type.dump call, you only need to provide 'value' since this is a copy and the db.mutables
  335. # stack will not need updating . . .
  336. 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()])
  337. self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE key=?", (name,))
  338. # And finally, store new pod_types to database
  339. self.cursor.table_state_set()
  340. def type_add_index(self, name, unique=False):
  341. unique = "" if unique is False else "UNIQUE "
  342. self.table_change_msg(msg="adding " + unique.lower() + "index '" + name + "' . . . ")
  343. self.cursor.execute("CREATE " + unique + "INDEX IF NOT EXISTS " + self.table + "_pin_" + name + " ON " + self.table + " (" + name + ")")
  344. def type_drop_index(self, name):
  345. self.table_change_msg(msg="dropping index '" + name + "' . . . ")
  346. self.cursor.execute("DROP INDEX IF EXISTS " + self.table + "_pin_" + name)
  347. def type_get_current_db_pod_types(self):
  348. self.cursor.execute("PRAGMA table_info(" + self.table + ")")
  349. return [str(col[1]) for col in self.cursor.fetchall() if str(col[1]) != 'id']
  350. def type_get_current_table_indexes(self):
  351. self.cursor.execute("PRAGMA index_list(" + self.table + ")")
  352. return [str(col[1]).replace(self.table + "_pin_", "") for col in self.cursor.fetchall()]
  353. def type_get_current_table_dict_indexes(self):
  354. self.cursor.execute("PRAGMA index_list(" + self.table_dict + ")")
  355. return [str(col[1]).replace(self.table + "_pin_", "") for col in self.cursor.fetchall()]
  356. """ instance methods """
  357. def inst_new(self, inst=None, kwargs=None):
  358. # This is for special pod_type types (like pod_type.TimeCreate) which want to do something on create . .
  359. for pod_type in set(self.type_callbacks_create) | set(self.type_callbacks_load):
  360. inst, kwargs = pod_type.on_inst_create(inst=inst, kwargs=kwargs)
  361. typed_values = {}
  362. dynamic_values = {}
  363. if kwargs is not None:
  364. object.__getattribute__(inst, '__dict__').update(kwargs) # This sets the objects dict
  365. for key, value in kwargs.iteritems():
  366. if key in self.typed_group:
  367. typed_values[key] = self.typed_group[key].dump(value = value, inst = inst)
  368. else:
  369. dynamic_values[key] = self.cursor.pickle_dump(value = value, inst = inst, attr = key)
  370. inst_id = self.cursor.insert_object(values = typed_values)
  371. self.cursor.add_many_kvargs(values = dynamic_values, inst_id = inst_id)
  372. object.__setattr__(inst, 'id', inst_id)
  373. self.cache[inst_id] = inst
  374. object.__getattribute__(inst, 'on_new_or_load_from_db')()
  375. #Then, add it to the list of 'fully loaded' instances
  376. self.fulls.add(inst)
  377. return inst
  378. def inst_update_dict(self, inst, kwargs):
  379. object.__getattribute__(inst, '__dict__').update(kwargs)
  380. type_attrs = ""
  381. type_values = []
  382. nom_values = []
  383. id = inst.id
  384. for key, value in kwargs.items():
  385. self.__setattr__(key, value)
  386. if key in self.typed_group:
  387. type_attrs += key + '=?,'
  388. type_values.append(self.typed_group[key].dump(value = value, inst = inst))
  389. else:
  390. nom_values.append((id, key,self.cursor.pickle_dump(value = value, inst = inst, attr = key),))
  391. if len(type_values) > 0:
  392. type_values.append(id)
  393. self.cursor.execute('UPDATE ' + self.table + ' SET ' + type_attrs[:-1] + ' WHERE id=?', type_values)
  394. if len(nom_values) > 0:
  395. self.cursor.executemany('INSERT OR REPLACE INTO ' + self.table_dict + ' (fid,key,value) VALUES (?,?,?)', nom_values)
  396. def inst_get_inst_by_id(self, inst_id, zombie=True):
  397. if inst_id in self.cache:
  398. return self.cache[inst_id]
  399. else:
  400. inst = self.cls.__new__(self.cls)
  401. if zombie:
  402. self.zombies.add(inst)
  403. object.__setattr__(inst, 'id', inst_id)
  404. object.__getattribute__(inst, 'on_new_or_load_from_db')()
  405. object.__getattribute__(inst, 'on_load_from_db')()
  406. self.cache[inst_id] = inst
  407. for pod_type in self.type_callbacks_load:
  408. inst = pod_type.on_inst_load(inst=inst)
  409. return inst
  410. def inst_load_attr_from_db(self, inst, attr):
  411. if attr in self.typed_group:
  412. self.cursor.execute("SELECT " + attr + " FROM " + self.table + " WHERE id=?", (object.__getattribute__(inst, 'id'),))
  413. row = self.cursor.fetchone()
  414. if row:
  415. self.zombies.discard(inst)
  416. object.__setattr__(inst, attr, self.typed_group[attr].load(row[0]))
  417. else:
  418. raise PodObjectDeleted, "The object " + str(inst) + " with id " + inst.get_full_id() + " had been deleted . . ."
  419. else:
  420. self.cursor.execute("SELECT value FROM " + self.table_dict + " WHERE fid=? AND key=?", (object.__getattribute__(inst, 'id'), attr,))
  421. row = self.cursor.fetchone()
  422. if row:
  423. self.zombies.discard(inst)
  424. object.__setattr__(inst, attr, self.cursor.pickle_load(value = row[0], inst = inst, attr = attr))
  425. else:
  426. self.inst_check_if_exists(inst)
  427. def inst_set_attr(self, inst, attr, value):
  428. if attr in Meta.POD_SETI_SET:
  429. raise PodObjectError, "You cannot set attr '" + attr + "' of a pod object . . ."
  430. if self.inst_check_if_exists(inst):
  431. object.__setattr__(inst, attr, value)
  432. self.inst_save_attr_to_db(inst, attr, value)
  433. def inst_save_attr_to_db(self, inst, attr, value):
  434. if attr in self.typed_group:
  435. self.cursor.execute('UPDATE ' + self.table + ' SET ' + attr + '=? WHERE id=?', (self.typed_group[attr].dump(value = value, inst = inst), inst.id,))
  436. else:
  437. 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),))
  438. def inst_del_attr(self, inst, attr):
  439. # 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)
  440. # If not in pod_type group, then delete it from the table.
  441. id = object.__getattribute__(inst, 'id')
  442. if attr in self.typed_group:
  443. self.cursor.execute('UPDATE ' + self.table + ' SET ' + attr + '=? WHERE id=?', (None,id,))
  444. object.__setattr__(inst, attr, None)
  445. else:
  446. self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE fid=? AND key=?", (id, attr,))
  447. dict = object.__getattribute__(inst, '__dict__')
  448. if attr in dict:
  449. del dict[attr]
  450. def inst_delete(self, inst):
  451. object.__getattribute__(inst, 'pre_delete')()
  452. id = object.__getattribute__(inst, 'id')
  453. self.cursor.execute("DELETE FROM " + self.table + " WHERE id=?", (id,))
  454. self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE fid=?", (id,))
  455. if inst in self.db.mutables:
  456. del self.db.mutables[inst]
  457. # THEN, MAKE IT A DELETED OBJECT()
  458. object.__setattr__(inst, '__class__', Deleted)
  459. object.__setattr__(inst, '__dict__', {})
  460. return None
  461. def inst_full_load(self, inst):
  462. if inst not in self.fulls:
  463. cursor = self.cursor
  464. id = object.__getattribute__(inst, 'id')
  465. inst_dict = object.__getattribute__(inst, '__dict__')
  466. pod_types = [pod_type for pod_type in self.typed_group.keys() if pod_type not in inst_dict]
  467. cursor.execute("SELECT " + ",".join(['id'] + pod_types) + " FROM " + self.table + " WHERE id=?", (id,))
  468. row = cursor.fetchone()
  469. if row:
  470. self.zombies.discard(inst)
  471. self.fulls.add(inst)
  472. for i, name in enumerate(pod_types):
  473. object.__setattr__(inst, name, self.typed_group[name].load(row[i+1]))
  474. cursor.execute("SELECT key,value FROM " + self.table_dict + " WHERE fid=?", (id,))
  475. rows = cursor.fetchall()
  476. for kv in rows:
  477. if kv[0] not in inst_dict:
  478. object.__setattr__(inst, kv[0], self.cursor.pickle_load(value = kv[1], inst = inst, attr = kv[0]))
  479. else:
  480. raise PodObjectDeleted, "The object " + str(inst) + " with id " + inst.get_full_id() + " had been deleted . . ."
  481. def inst_clear_all(self, inst):
  482. id = object.__getattribute__(inst, 'id')
  483. sets = ""
  484. args = []
  485. new_dict = {'id': id}
  486. for type in self.typed_group.keys():
  487. sets += type + '=?,'
  488. args.append(None)
  489. new_dict[type] = None
  490. if len(args) > 0:
  491. args.append(id)
  492. self.cursor.execute('UPDATE ' + self.table + ' SET ' + sets[:-1] + ' WHERE id=?', args)
  493. self.cursor.execute("DELETE FROM " + self.table_dict + " WHERE fid=?", (id,))
  494. object.__setattr__(inst, '__dict__', new_dict)
  495. def inst_len(self, inst, id = False):
  496. if inst in self.fulls:
  497. return len(object.__getattribute__(inst,'__dict__')) - (0 if id else 1)
  498. else:
  499. 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)
  500. def inst_contains(self, inst, key):
  501. if inst in self.fulls:
  502. return key in object.__getattribute__(inst,'__dict__')
  503. else:
  504. if key in self.typed_group:
  505. return True
  506. else:
  507. return bool(self.cursor.execute('SELECT COUNT(rowid) FROM ' + self.table_dict + ' WHERE fid=? AND key=?', (object.__getattribute__(inst,'id'),key,)).fetchone()[0])
  508. def inst_check_if_exists(self, inst, error = True):
  509. if inst not in self.zombies:
  510. return True
  511. else:
  512. self.cursor.execute("SELECT id FROM " + self.table + " WHERE id=?", (object.__getattribute__(inst, 'id'),))
  513. row = self.cursor.fetchone()
  514. if row:
  515. self.zombies.discard(inst)
  516. elif error:
  517. raise PodObjectDeleted, "The object " + str(inst) + " with id " + inst.get_full_id() + " has been deleted . . ."
  518. class PodNoDatabase(object):
  519. def __getattribute__(self, key):
  520. if key == 'attach_to_db':
  521. object.__setattr__(self, '__class__', type.__getattribute__(object.__getattribute__(self, 'cls'), 'Pod'))
  522. return object.__getattribute__(self, key)
  523. elif key in ['id', 'child_pods']:
  524. return object.__getattribute__(self, key)
  525. else:
  526. raise PodNoDatabaseError, "You have not connected the class '" + str(object.__getattribute__(self, 'cls')) + "' to any database . . . "
  527. def __setattr__(self, key, value):
  528. raise PodNoDatabaseError, "You have not connected the class '" + str(object.__getattribute__(self, 'cls')) + "' to any database . . . "
  529. class Where(object):
  530. def __init__(self, cls_pod):
  531. self.cls_pod = cls_pod
  532. def __getattr__(self, key):
  533. if key in self.cls_pod.typed_group:
  534. return self.cls_pod.typed_group[key]
  535. else:
  536. return typed.Dynamic(cls_pod=self.cls_pod, name=key)
  537. """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
  538. ##
  539. ## Object: THE OBJECT IN P'O'D
  540. ##
  541. """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
  542. class PodObjectError(exceptions.BaseException):
  543. pass
  544. class PodObjectDeleted(exceptions.BaseException):
  545. pass
  546. class PodObjectUndefined(exceptions.BaseException):
  547. pass
  548. class Object(object):
  549. __metaclass__ = Meta
  550. def __init__(self, **kwargs):
  551. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_new(inst=self, kwargs=kwargs)
  552. def __getattribute__(self, attr):
  553. dict = object.__getattribute__(self, '__dict__')
  554. if attr in dict:
  555. value = dict[attr]
  556. elif attr == '__dict__':
  557. return dict
  558. else:
  559. cls_pod = type.__getattribute__(object.__getattribute__(self, '__class__'), 'pod')
  560. if attr in cls_pod.typed_group:
  561. cls_pod.inst_load_attr_from_db(self, attr)
  562. value = dict[attr]
  563. else:
  564. return object.__getattribute__(self, attr) # This, then, will call __getattr__ on fail and try to load from database . . . '
  565. return value
  566. def __getattr__(self, attr):
  567. dict = object.__getattribute__(self, '__dict__')
  568. cls_pod = object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod')
  569. cls_pod.inst_load_attr_from_db(self, attr)
  570. if attr in dict:
  571. return dict[attr]
  572. else:
  573. raise AttributeError, "'" + self.__class__.__name__ + "' object has no attribute '" + attr + "'"
  574. def __setattr__(self, attr, value):
  575. return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_set_attr(self, attr, value)
  576. def __delattr__(self, attr):
  577. return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_del_attr(self, attr)
  578. def delete(self):
  579. return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_delete(self)
  580. def full_load(self):
  581. return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  582. def get_full_id(self):
  583. return str(type.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').id) + ":" + str(object.__getattribute__(self, 'id'))
  584. def set_many(self, **kwargs):
  585. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_update_dict(self, kwargs)
  586. def update(self, other):
  587. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_update_dict(self, other)
  588. """ pass through convenience functions """
  589. def on_new_or_load_from_db(self):
  590. pass
  591. def on_load_from_db(self):
  592. pass
  593. def pre_delete(self):
  594. pass
  595. """ dictionary interface """
  596. def __len__(self, id = False):
  597. return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_len(self, id = False)
  598. def __getitem__(self, key):
  599. return getattr(self, key)
  600. def __setitem__(self, key, value):
  601. return object.__getattribute__(self, '__setattr__')(key, value)
  602. def __contains__(self, key):
  603. return object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_contains(inst = self, key = key)
  604. def __iter__(self):
  605. return self.iterkeys(id = False)
  606. def clear(self):
  607. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_clear_all(self)
  608. def copy(self, id = False):
  609. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  610. new_dict = object.__getattribute__(self, '__dict__').copy()
  611. if id is False:
  612. del new_dict['id']
  613. return new_dict
  614. def get(self, key, default = None):
  615. return getattr(self, key, default)
  616. def setdefault(self, key, default):
  617. try:
  618. return getattr(self, key)
  619. except AttributeError:
  620. object.__getattribute__(self, '__setattr__')(key, default)
  621. return default
  622. def keys(self, id = False):
  623. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  624. return [key for key in object.__getattribute__(self, '__dict__').iterkeys() if key != 'id' or id]
  625. def iterkeys(self, id = False):
  626. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  627. return (key for key in object.__getattribute__(self, '__dict__').iterkeys() if key != 'id' or id)
  628. def values(self, id = False):
  629. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  630. return [value for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id]
  631. def itervalues(self, id = False):
  632. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  633. return (value for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id)
  634. def items(self, id = False):
  635. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  636. return [(key,value) for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id]
  637. def iteritems(self, id = False):
  638. object.__getattribute__(object.__getattribute__(self, '__class__'), 'pod').inst_full_load(self)
  639. return ((key,value) for key,value in object.__getattribute__(self, '__dict__').iteritems() if key != 'id' or id)
  640. class Undefined(Object):
  641. POD_GETA_SET = set(['__class__', '__init__', '__reduce__', '__reduce_ex__', 'get_full_id'])
  642. def __init__(self, id, cache):
  643. object.__setattr__(self, 'id', id)
  644. object.__setattr__(self, 'cache', cache)
  645. def get_full_id(self):
  646. id = object.__getattribute__(self, 'id')
  647. return str(id[0]) + ":" + str(id[1])
  648. def __getattribute__(self, key):
  649. if key in Undefined.POD_GETA_SET:
  650. return object.__getattribute__(self, key)
  651. else:
  652. object.__getattribute__(self, 'try_to_activate_class')(key)
  653. # The above function changes the class, preventing an infinite loop
  654. return getattr(self, key)
  655. def __setattr__(self, key, value):
  656. object.__getattribute__(self, 'try_to_activate_class')(key)
  657. self.__setattr__(key, value)
  658. def try_to_activate_class(self, key):
  659. id = object.__getattribute__(self, 'id')
  660. cache = object.__getattribute__(self, 'cache')
  661. cls_id, inst_id = id[0], id[1]
  662. if cls_id in cache.class_pods:
  663. cls_pod = cache.class_pods[cls_id]
  664. inst_id = id[1]
  665. object.__setattr__(self, '__class__', cls_pod.cls)
  666. object.__setattr__(self, 'id', inst_id)
  667. object.__delattr__(self, 'cache')
  668. cls_pod.cache[inst_id] = self
  669. else:
  670. 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. . . "
  671. """ Things you don't want to save """
  672. class NoSave(object):
  673. def get_full_id(self):
  674. return "0:0"
  675. class Deleted(NoSave):
  676. def get_full_id(self):
  677. return "0:0"
  678. def __getattribute__(self, key):
  679. 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 . . . "
  680. def __setattr__(self, key, value):
  681. 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 . . . "