Mercurial > p > roundup > code
diff roundup/backends/sessions_sqlite.py @ 6804:25d08e15e3b4
issue2551224 - Replace dbm db for sessions/otks when using sqlite
Generate new sqlite db's for storing one time keys and session and
other ephemeral data.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Mon, 25 Jul 2022 15:30:36 -0400 |
| parents | |
| children | 375d40a9e730 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roundup/backends/sessions_sqlite.py Mon Jul 25 15:30:36 2022 -0400 @@ -0,0 +1,189 @@ +"""This module defines a very basic store that's used by the CGI interface +to store session and one-time-key information. + +Yes, it's called "sessions" - because originally it only defined a session +class. It's now also used for One Time Key handling too. + +We needed to split commits to session/OTK database from commits on the +main db structures (user data). This required two connections to the +sqlite db, which wasn't supported. This module was created so sqlite +didn't have to use dbm for the session/otk data. It hopefully will +provide a performance speedup. +""" +__docformat__ = 'restructuredtext' +import os, time, logging + +from roundup.anypy.html import html_escape as escape + +class BasicDatabase: + ''' Provide a nice encapsulation of an RDBMS table. + + Keys are id strings, values are automatically marshalled data. + ''' + name = None + def __init__(self, db): + self.db = db + self.conn, self.cursor = self.db.sql_open_connection(dbname=self.name) + + self.sql('''SELECT name FROM sqlite_master WHERE type='table' AND name='%ss';'''%self.name) + table_exists = self.cursor.fetchone() + + if not table_exists: + # create table/rows etc. + self.sql('''CREATE TABLE %(name)ss (%(name)s_key VARCHAR(255), + %(name)s_value TEXT, %(name)s_time REAL)'''%{"name":self.name}) + self.sql('CREATE INDEX %(name)s_key_idx ON %(name)ss(%(name)s_key)'%{"name":self.name}) + self.commit() + + def log_debug(self, msg, *args, **kwargs): + """Log a message with level DEBUG.""" + + logger = self.get_logger() + logger.debug(msg, *args, **kwargs) + + def log_info(self, msg, *args, **kwargs): + """Log a message with level INFO.""" + + logger = self.get_logger() + logger.info(msg, *args, **kwargs) + + def get_logger(self): + """Return the logger for this database.""" + + # Because getting a logger requires acquiring a lock, we want + # to do it only once. + if not hasattr(self, '__logger'): + self.__logger = logging.getLogger('roundup') + + return self.__logger + + def sql(self, sql, args=None, cursor=None): + """ Execute the sql with the optional args. + """ + self.log_debug('SQL %r %r' % (sql, args)) + if not cursor: + cursor = self.cursor + if args: + cursor.execute(sql, args) + else: + cursor.execute(sql) + + def clear(self): + self.cursor.execute('delete from %ss'%self.name) + + def exists(self, infoid): + n = self.name + self.cursor.execute('select count(*) from %ss where %s_key=%s'%(n, + n, self.db.arg), (infoid,)) + return int(self.cursor.fetchone()[0]) + + _marker = [] + def get(self, infoid, value, default=_marker): + n = self.name + self.cursor.execute('select %s_value from %ss where %s_key=%s'%(n, + n, n, self.db.arg), (infoid,)) + res = self.cursor.fetchone() + if not res: + if default != self._marker: + return default + raise KeyError('No such %s "%s"'%(self.name, escape(infoid))) + values = eval(res[0]) + return values.get(value, None) + + def getall(self, infoid): + n = self.name + self.cursor.execute('select %s_value from %ss where %s_key=%s'%(n, + n, n, self.db.arg), (infoid,)) + res = self.cursor.fetchone() + if not res: + raise KeyError('No such %s "%s"'%(self.name, escape (infoid))) + return eval(res[0]) + + def set(self, infoid, **newvalues): + """ Store all newvalues under key infoid with a timestamp in database. + + If newvalues['__timestamp'] exists and is representable as a floating point number + (i.e. could be generated by time.time()), that value is used for the <name>_time + column in the database. + """ + c = self.cursor + n = self.name + a = self.db.arg + c.execute('select %s_value from %ss where %s_key=%s'%(n, n, n, a), + (infoid,)) + res = c.fetchone() + if res: + values = eval(res[0]) + else: + values = {} + values.update(newvalues) + + if res: + sql = 'update %ss set %s_value=%s where %s_key=%s'%(n, n, + a, n, a) + args = (repr(values), infoid) + else: + if '__timestamp' in newvalues: + try: + # __timestamp must be represntable as a float. Check it. + timestamp = float(newvalues['__timestamp']) + except ValueError: + timestamp = time.time() + else: + timestamp = time.time() + + sql = 'insert into %ss (%s_key, %s_time, %s_value) '\ + 'values (%s, %s, %s)'%(n, n, n, n, a, a, a) + args = (infoid, timestamp, repr(values)) + c.execute(sql, args) + + def list(self): + c = self.cursor + n = self.name + c.execute('select %s_key from %ss'%(n, n)) + return [res[0] for res in c.fetchall()] + + def destroy(self, infoid): + self.cursor.execute('delete from %ss where %s_key=%s'%(self.name, + self.name, self.db.arg), (infoid,)) + + def updateTimestamp(self, infoid): + """ don't update every hit - once a minute should be OK """ + now = time.time() + self.cursor.execute('''update %ss set %s_time=%s where %s_key=%s + and %s_time < %s'''%(self.name, self.name, self.db.arg, + self.name, self.db.arg, self.name, self.db.arg), + (now, infoid, now-60)) + + def clean(self): + ''' Remove session records that haven't been used for a week. ''' + now = time.time() + week = 60*60*24*7 + old = now - week + self.cursor.execute('delete from %ss where %s_time < %s'%(self.name, + self.name, self.db.arg), (old, )) + + def lifetime(self, key_lifetime=None): + """Return the proper timestamp for a key with key_lifetime specified + in seconds. + """ + now = time.time() + week = 60*60*24*7 + return now - week + lifetime + + def commit(self): + logger = logging.getLogger('roundup.hyperdb.backend') + logger.info('commit %s' % self.name) + self.conn.commit() + self.cursor = self.conn.cursor() + + def close(self): + self.conn.close() + +class Sessions(BasicDatabase): + name = 'session' + +class OneTimeKeys(BasicDatabase): + name = 'otk' + +# vim: set et sts=4 sw=4 :
