# vim: set filencoding=utf8 """ Storage Back-ends For Kronos @author: Mike Crute (mcrute@ag.com) @organization: American Greetings Interactive @date: February 04, 2010 """ class StorageError(Exception): """ Base class for all other storage exceptions. """ class NotConnected(StorageError): """ Raised when a connection to the back-end is not avaiable. """ def _get_filtered_object_dict(dict_): output = {} for key, value in dict_.__dict__.items(): if not key.startswith('_') and not callable(value): output[key] = value return output class BaseStorageBackEnd(object): def __init__(self): self.load_engine() def get(self, model_obj, **kwargs): """ Get a model object instance from the storage back-end where the criteria in the kwargs matches that in the back-end. """ name = model_obj.__name__ result = self.select(name, **kwargs) instance = model_obj() for key, value in result.items(): if key != 'id': setattr(instance, key, value) else: instance.__db_id__ = value return instance def save(self, model_obj): """ Persist a model object with the storage back-end. """ name = model_obj.__class__.__name__ id_ = getattr(model_obj, '__db_id__', None) values = _get_filtered_object_dict(model_obj) if id_: self.update(name, id_, **values) else: self.insert(name, **values) def load_engine(self): """ Load a storage engine module that contains a function called connect suitable for setting up the actual connection. """ def select(self, table, **kwargs): """ Select raw data from the storage engine and return as a dictionary. """ def insert(self, table, **values): """ Insert values into the back-end named table or equivalent data structure. """ def update(self, table, criteria, **values): """ Update values in the back-end table or equivalent data structure based on the criteria. """ def connect(self, database, *args, **kwargs): """ Create a connection to the back-end. """ def create_table(self, obj, **columns): """ Create a table or equivalent data structure in the storage back-end. """ class SQLiteBackEnd(BaseStorageBackEnd): def __init__(self): super(SQLiteBackEnd, self).__init__() self.connection = None def load_engine(self): import sqlite3 self.engine = sqlite3 def _check_connection(self): if not self.connection: raise NotConnected("Not connected to storage back-end!") def connect(self, database, *args, **kwargs): self.connection = self.engine.connect(database) def select(self, table, **kwargs): sql = "SELECT * FROM {0} WHERE ".format(table) for key in kwargs.keys(): sql += "{0}=? ".format(key) return self._get_normalized_results(sql, **kwargs)[0] def _get_normalized_results(self, sql, **kwargs): self._check_connection() cursor = self.connection.cursor() results = cursor.execute(sql, kwargs.values()) return self._normalize_results(results) def _normalize_results(self, results): col_names = [col[0] for col in results.description] output = [] for row in results.fetchall(): output.append(dict(zip(col_names, row))) return output def insert(self, table, **values): placeholder = ("?," * len(values)).rstrip(',') col_spec = ','.join(values.keys()) sql = "INSERT INTO {0}({1}) VALUES({2})".format(table, col_spec, placeholder) self._execute_modification(sql, values.values()) def update(self, table, criteria, **values): sql = "UPDATE {0} SET ".format(table) for key in values.keys(): sql += "{0}=?, ".format(key) sql = sql[:-2] + "WHERE id=?" binds = values.values() + [criteria] self._execute_modification(sql, binds) def create_table(self, obj, **columns): table = obj.__name__ sql = "CREATE TABLE {0} (".format(table) sql += "id INTEGER PRIMARY KEY" for key, value in columns.items(): sql += ", {0} {1}".format(key, value) sql += ")" self._execute_modification(sql) def _execute_modification(self, sql, binds=()): self._check_connection() cursor = self.connection.cursor() cursor.execute(sql, binds) self.connection.commit()