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 :

Roundup Issue Tracker: http://roundup-tracker.org/