Mercurial > p > roundup > code
comparison roundup/backends/sessions_rdbms.py @ 6823:fe0091279f50
Refactor session db logging and key generation for sessions/otks
While I was working on the redis sessiondb stuff, I noticed that
log_wanrning, get_logger ... was duplicated. Also there was code to
generate a unique key for otks that was duplicated.
Changes:
creating new sessions_common.py and SessionsCommon class to provide
methods:
log_warning, log_info, log_debug, get_logger, getUniqueKey
getUniqueKey method is closer to the method used to make
session keys in client.py.
sessions_common.py now report when random_.py chooses a weak
random number generator. Removed same from rest.py.
get_logger reconciles all logging under
roundup.hyperdb.backends.<name of BasicDatabase class>
some backends used to log to root logger.
have BasicDatabase in other sessions_*.py modules inherit from
SessionCommon.
change logging to use log_* methods.
In addition:
remove unused imports reported by flake8 and other formatting
changes
modify actions.py, rest.py, templating.py to use getUniqueKey
method.
add tests for new methods
test_redis_session.py
swap out ModuleNotFoundError for ImportError to prevent crash in
python2 when redis is not present.
allow injection of username:password or just password into redis
connection URL. set pytest_redis_pw envirnment variable to password
or user:password when running test.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Sun, 07 Aug 2022 01:51:11 -0400 |
| parents | 375d40a9e730 |
| children | ee17f62c8341 |
comparison
equal
deleted
inserted
replaced
| 6822:5053ee6c846b | 6823:fe0091279f50 |
|---|---|
| 3 | 3 |
| 4 Yes, it's called "sessions" - because originally it only defined a session | 4 Yes, it's called "sessions" - because originally it only defined a session |
| 5 class. It's now also used for One Time Key handling too. | 5 class. It's now also used for One Time Key handling too. |
| 6 """ | 6 """ |
| 7 __docformat__ = 'restructuredtext' | 7 __docformat__ = 'restructuredtext' |
| 8 import os, time, logging | 8 import time |
| 9 | 9 |
| 10 from roundup.anypy.html import html_escape as escape | 10 from roundup.anypy.html import html_escape as escape |
| 11 from roundup.backends.sessions_common import SessionCommon | |
| 11 | 12 |
| 12 class BasicDatabase: | 13 |
| 14 class BasicDatabase(SessionCommon): | |
| 13 ''' Provide a nice encapsulation of an RDBMS table. | 15 ''' Provide a nice encapsulation of an RDBMS table. |
| 14 | 16 |
| 15 Keys are id strings, values are automatically marshalled data. | 17 Keys are id strings, values are automatically marshalled data. |
| 16 ''' | 18 ''' |
| 17 name = None | 19 name = None |
| 20 | |
| 18 def __init__(self, db): | 21 def __init__(self, db): |
| 19 self.db = db | 22 self.db = db |
| 20 self.conn, self.cursor = self.db.sql_open_connection() | 23 self.conn, self.cursor = self.db.sql_open_connection() |
| 21 | 24 |
| 22 def clear(self): | 25 def clear(self): |
| 23 self.cursor.execute('delete from %ss'%self.name) | 26 self.cursor.execute('delete from %ss' % self.name) |
| 24 | 27 |
| 25 def exists(self, infoid): | 28 def exists(self, infoid): |
| 26 n = self.name | 29 n = self.name |
| 27 self.cursor.execute('select count(*) from %ss where %s_key=%s'%(n, | 30 self.cursor.execute('select count(*) from %ss where %s_key=%s' % |
| 28 n, self.db.arg), (infoid,)) | 31 (n, n, self.db.arg), (infoid,)) |
| 29 return int(self.cursor.fetchone()[0]) | 32 return int(self.cursor.fetchone()[0]) |
| 30 | 33 |
| 31 _marker = [] | 34 _marker = [] |
| 35 | |
| 32 def get(self, infoid, value, default=_marker): | 36 def get(self, infoid, value, default=_marker): |
| 33 n = self.name | 37 n = self.name |
| 34 self.cursor.execute('select %s_value from %ss where %s_key=%s'%(n, | 38 self.cursor.execute('select %s_value from %ss where %s_key=%s' % |
| 35 n, n, self.db.arg), (infoid,)) | 39 (n, n, n, self.db.arg), (infoid,)) |
| 36 res = self.cursor.fetchone() | 40 res = self.cursor.fetchone() |
| 37 if not res: | 41 if not res: |
| 38 if default != self._marker: | 42 if default != self._marker: |
| 39 return default | 43 return default |
| 40 raise KeyError('No such %s "%s"'%(self.name, escape(infoid))) | 44 raise KeyError('No such %s "%s"' % (self.name, escape(infoid))) |
| 41 values = eval(res[0]) | 45 values = eval(res[0]) |
| 42 return values.get(value, None) | 46 return values.get(value, None) |
| 43 | 47 |
| 44 def getall(self, infoid): | 48 def getall(self, infoid): |
| 45 n = self.name | 49 n = self.name |
| 46 self.cursor.execute('select %s_value from %ss where %s_key=%s'%(n, | 50 self.cursor.execute('select %s_value from %ss where %s_key=%s' % |
| 47 n, n, self.db.arg), (infoid,)) | 51 (n, n, n, self.db.arg), (infoid,)) |
| 48 res = self.cursor.fetchone() | 52 res = self.cursor.fetchone() |
| 49 if not res: | 53 if not res: |
| 50 raise KeyError('No such %s "%s"'%(self.name, escape (infoid))) | 54 raise KeyError('No such %s "%s"' % (self.name, escape(infoid))) |
| 51 return eval(res[0]) | 55 return eval(res[0]) |
| 52 | 56 |
| 53 def set(self, infoid, **newvalues): | 57 def set(self, infoid, **newvalues): |
| 54 """ Store all newvalues under key infoid with a timestamp in database. | 58 """ Store all newvalues under key infoid with a timestamp in database. |
| 55 | 59 |
| 56 If newvalues['__timestamp'] exists and is representable as a floating point number | 60 If newvalues['__timestamp'] exists and is representable as |
| 57 (i.e. could be generated by time.time()), that value is used for the <name>_time | 61 a floating point number (i.e. could be generated by time.time()), |
| 58 column in the database. | 62 that value is used for the <name>_time column in the database. |
| 59 """ | 63 """ |
| 60 c = self.cursor | 64 c = self.cursor |
| 61 n = self.name | 65 n = self.name |
| 62 a = self.db.arg | 66 a = self.db.arg |
| 63 c.execute('select %s_value from %ss where %s_key=%s'% \ | 67 c.execute('select %s_value from %ss where %s_key=%s' % |
| 64 (n, n, n, a), | 68 (n, n, n, a), (infoid,)) |
| 65 (infoid,)) | |
| 66 res = c.fetchone() | 69 res = c.fetchone() |
| 67 | 70 |
| 68 timestamp=time.time() | 71 timestamp = time.time() |
| 69 if res: | 72 if res: |
| 70 values = eval(res[0]) | 73 values = eval(res[0]) |
| 71 else: | 74 else: |
| 72 values = {} | 75 values = {} |
| 73 | 76 |
| 83 # here timestamp is the new timestamp | 86 # here timestamp is the new timestamp |
| 84 newvalues['__timestamp'] = timestamp | 87 newvalues['__timestamp'] = timestamp |
| 85 values.update(newvalues) | 88 values.update(newvalues) |
| 86 if res: | 89 if res: |
| 87 sql = ('update %ss set %s_value=%s, %s_time=%s ' | 90 sql = ('update %ss set %s_value=%s, %s_time=%s ' |
| 88 'where %s_key=%s'%(n, n, a, n, a, n, a)) | 91 'where %s_key=%s' % (n, n, a, n, a, n, a)) |
| 89 args = (repr(values), timestamp, infoid) | 92 args = (repr(values), timestamp, infoid) |
| 90 else: | 93 else: |
| 91 sql = 'insert into %ss (%s_key, %s_time, %s_value) '\ | 94 sql = 'insert into %ss (%s_key, %s_time, %s_value) '\ |
| 92 'values (%s, %s, %s)'%(n, n, n, n, a, a, a) | 95 'values (%s, %s, %s)' % (n, n, n, n, a, a, a) |
| 93 args = (infoid, timestamp, repr(values)) | 96 args = (infoid, timestamp, repr(values)) |
| 94 c.execute(sql, args) | 97 c.execute(sql, args) |
| 95 | 98 |
| 96 def list(self): | 99 def list(self): |
| 97 c = self.cursor | 100 c = self.cursor |
| 98 n = self.name | 101 n = self.name |
| 99 c.execute('select %s_key from %ss'%(n, n)) | 102 c.execute('select %s_key from %ss' % (n, n)) |
| 100 return [res[0] for res in c.fetchall()] | 103 return [res[0] for res in c.fetchall()] |
| 101 | 104 |
| 102 def destroy(self, infoid): | 105 def destroy(self, infoid): |
| 103 self.cursor.execute('delete from %ss where %s_key=%s'%(self.name, | 106 self.cursor.execute('delete from %ss where %s_key=%s' % |
| 104 self.name, self.db.arg), (infoid,)) | 107 (self.name, self.name, self.db.arg), (infoid,)) |
| 105 | 108 |
| 106 def updateTimestamp(self, infoid): | 109 def updateTimestamp(self, infoid): |
| 107 """ don't update every hit - once a minute should be OK """ | 110 """ don't update every hit - once a minute should be OK """ |
| 108 now = time.time() | 111 now = time.time() |
| 109 self.cursor.execute('''update %ss set %s_time=%s where %s_key=%s | 112 self.cursor.execute('''update %ss set %s_time=%s where %s_key=%s ''' |
| 110 and %s_time < %s'''%(self.name, self.name, self.db.arg, | 113 '''and %s_time < %s''' % |
| 111 self.name, self.db.arg, self.name, self.db.arg), | 114 (self.name, self.name, self.db.arg, self.name, |
| 112 (now, infoid, now-60)) | 115 self.db.arg, self.name, self.db.arg), |
| 116 (now, infoid, now-60)) | |
| 113 | 117 |
| 114 def clean(self): | 118 def clean(self): |
| 115 ''' Remove session records that haven't been used for a week. ''' | 119 ''' Remove session records that haven't been used for a week. ''' |
| 116 now = time.time() | 120 now = time.time() |
| 117 week = 60*60*24*7 | 121 week = 60*60*24*7 |
| 118 old = now - week | 122 old = now - week |
| 119 self.cursor.execute('delete from %ss where %s_time < %s'%(self.name, | 123 self.cursor.execute('delete from %ss where %s_time < %s' % |
| 120 self.name, self.db.arg), (old, )) | 124 (self.name, self.name, self.db.arg), (old, )) |
| 121 | 125 |
| 122 def commit(self): | 126 def commit(self): |
| 123 logger = logging.getLogger('roundup.hyperdb.backend') | 127 self.log_info('commit %s' % self.name) |
| 124 logger.info('commit %s' % self.name) | |
| 125 self.conn.commit() | 128 self.conn.commit() |
| 126 self.cursor = self.conn.cursor() | 129 self.cursor = self.conn.cursor() |
| 127 | 130 |
| 128 def lifetime(self, item_lifetime=0): | 131 def lifetime(self, item_lifetime=0): |
| 129 """Return the proper timestamp for a key with key_lifetime specified | 132 """Return the proper timestamp for a key with key_lifetime specified |
| 134 return now - week + item_lifetime | 137 return now - week + item_lifetime |
| 135 | 138 |
| 136 def close(self): | 139 def close(self): |
| 137 self.conn.close() | 140 self.conn.close() |
| 138 | 141 |
| 142 | |
| 139 class Sessions(BasicDatabase): | 143 class Sessions(BasicDatabase): |
| 140 name = 'session' | 144 name = 'session' |
| 145 | |
| 141 | 146 |
| 142 class OneTimeKeys(BasicDatabase): | 147 class OneTimeKeys(BasicDatabase): |
| 143 name = 'otk' | 148 name = 'otk' |
| 144 | 149 |
| 145 # vim: set et sts=4 sw=4 : | 150 # vim: set et sts=4 sw=4 : |
