X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Futil%2Fdata.py;h=26fa8599a8655c52ff2a35744b1aff3ff775b9ac;hp=0e086b35f78ca8352a63438ab4fa6b275394666e;hb=abcefb0f2eece549371f951b58144188d2ac9307;hpb=b15a773ee9257f0102f24b6f634cf55a5948a443 diff --git a/ipsilon/util/data.py b/ipsilon/util/data.py old mode 100755 new mode 100644 index 0e086b3..26fa859 --- a/ipsilon/util/data.py +++ b/ipsilon/util/data.py @@ -1,5 +1,3 @@ -#!/usr/bin/python -# # Copyright (C) 2013 Simo Sorce # # see file 'COPYING' for use and warranty information @@ -21,10 +19,12 @@ import cherrypy from ipsilon.util.log import Log from sqlalchemy import create_engine from sqlalchemy import MetaData, Table, Column, Text +from sqlalchemy.pool import QueuePool, SingletonThreadPool from sqlalchemy.sql import select import ConfigParser import os import uuid +import logging OPTIONS_COLUMNS = ['name', 'option', 'value'] @@ -32,19 +32,53 @@ UNIQUE_DATA_COLUMNS = ['uuid', 'name', 'value'] class SqlStore(Log): + __instances = {} + + @classmethod + def get_connection(cls, name): + if name not in cls.__instances.keys(): + if cherrypy.config.get('db.conn.log', False): + logging.debug('SqlStore new: %s', name) + cls.__instances[name] = SqlStore(name) + return cls.__instances[name] def __init__(self, name): + self.db_conn_log = cherrypy.config.get('db.conn.log', False) + self.debug('SqlStore init: %s' % name) + self.name = name engine_name = name if '://' not in engine_name: engine_name = 'sqlite:///' + engine_name - self._dbengine = create_engine(engine_name) + # This pool size is per configured database. The minimum needed, + # determined by binary search, is 23. We're using 25 so we have a bit + # more playroom, and then the overflow should make sure things don't + # break when we suddenly need more. + pool_args = {'poolclass': QueuePool, + 'pool_size': 25, + 'max_overflow': 50} + if engine_name.startswith('sqlite://'): + # It's not possible to share connections for SQLite between + # threads, so let's use the SingletonThreadPool for them + pool_args = {'poolclass': SingletonThreadPool} + self._dbengine = create_engine(engine_name, **pool_args) self.is_readonly = False + def debug(self, fact): + if self.db_conn_log: + super(SqlStore, self).debug(fact) + def engine(self): return self._dbengine def connection(self): - return self._dbengine.connect() + self.debug('SqlStore connect: %s' % self.name) + conn = self._dbengine.connect() + + def cleanup_connection(): + self.debug('SqlStore cleanup: %s' % self.name) + conn.close() + cherrypy.request.hooks.attach('on_end_request', cleanup_connection) + return conn def SqlAutotable(f): @@ -138,6 +172,7 @@ class FileStore(Log): timestamp = stat.st_mtime if self._config is None or timestamp > self._timestamp: self._config = ConfigParser.RawConfigParser() + self._config.optionxform = str self._config.read(self._filename) return self._config @@ -231,16 +266,21 @@ class FileQuery(Log): class Store(Log): - def __init__(self, config_name): - if config_name not in cherrypy.config: - raise NameError('Unknown database %s' % config_name) - name = cherrypy.config[config_name] + def __init__(self, config_name=None, database_url=None): + if config_name is None and database_url is None: + raise ValueError('config_name or database_url must be provided') + if config_name: + if config_name not in cherrypy.config: + raise NameError('Unknown database %s' % config_name) + name = cherrypy.config[config_name] + else: + name = database_url if name.startswith('configfile://'): _, filename = name.split('://') self._db = FileStore(filename) self._query = FileQuery else: - self._db = SqlStore(name) + self._db = SqlStore.get_connection(name) self._query = SqlQuery @property @@ -373,10 +413,14 @@ class Store(Log): datum = data[uid] for name in datum: if name in curvals: - q.update({'value': datum[name]}, - {'uuid': uid, 'name': name}) + if datum[name] is None: + q.delete({'uuid': uid, 'name': name}) + else: + q.update({'value': datum[name]}, + {'uuid': uid, 'name': name}) else: - q.insert((uid, name, datum[name])) + if datum[name] is not None: + q.insert((uid, name, datum[name])) q.commit() except Exception, e: # pylint: disable=broad-except @@ -394,6 +438,7 @@ class Store(Log): self.error("Failed to delete data from %s: [%s]" % (table, e)) def _reset_data(self, table): + q = None try: q = self._query(self._db, table, UNIQUE_DATA_COLUMNS) q.drop()