1 """GNUmed configuration handling.
2
3 This source of configuration information is supported:
4
5 - database tables
6
7 Theory of operation:
8
9 It is helpful to have a solid log target set up before importing this
10 module in your code. This way you will be able to see even those log
11 messages generated during module import.
12
13 Once your software has established database connectivity you can
14 set up a config source from the database. You can limit the option
15 applicability by the constraints "workplace", "user", and "cookie".
16
17 The basic API for handling items is get()/set().
18 The database config objects auto-sync with the backend.
19
20 @copyright: GPL
21 """
22
23
24
25 __version__ = "$Revision: 1.60 $"
26 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
27
28
29 import sys, types, cPickle, decimal, logging, re as regex
30
31
32
33 if __name__ == '__main__':
34 sys.path.insert(0, '../../')
35 from Gnumed.pycommon import gmPG2, gmTools
36
37
38 _log = logging.getLogger('gm.cfg')
39 _log.info(__version__)
40
41
42
43 cfg_DEFAULT = "xxxDEFAULTxxx"
44
46
47 if order_by is None:
48 order_by = u''
49 else:
50 order_by = u'ORDER BY %s' % order_by
51
52 cmd = u"""
53 SELECT * FROM (
54
55 SELECT
56 vco.*,
57 cs.value
58 FROM
59 cfg.v_cfg_options vco JOIN cfg.cfg_string cs ON (vco.pk_cfg_item = cs.fk_item)
60
61 UNION ALL
62
63 SELECT
64 vco.*,
65 cn.value::text
66 FROM
67 cfg.v_cfg_options vco JOIN cfg.cfg_numeric cn ON (vco.pk_cfg_item = cn.fk_item)
68
69 UNION ALL
70
71 SELECT
72 vco.*,
73 csa.value::text
74 FROM
75 cfg.v_cfg_options vco JOIN cfg.cfg_str_array csa ON (vco.pk_cfg_item = csa.fk_item)
76
77 UNION ALL
78
79 SELECT
80 vco.*,
81 cd.value::text
82 FROM
83 cfg.v_cfg_options vco JOIN cfg.cfg_data cd ON (vco.pk_cfg_item = cd.fk_item)
84
85 ) as option_list
86 %s""" % order_by
87
88 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx = False)
89
90 return rows
91
92
96
97
98
99 - def get(self, option=None, workplace=None, cookie=None, bias=None, default=None, sql_return_type=None):
100 return self.get2 (
101 option = option,
102 workplace = workplace,
103 cookie = cookie,
104 bias = bias,
105 default = default,
106 sql_return_type = sql_return_type
107 )
108
109 - def get2(self, option=None, workplace=None, cookie=None, bias=None, default=None, sql_return_type=None):
110 """Retrieve configuration option from backend.
111
112 @param bias: Determine the direction into which to look for config options.
113
114 'user': When no value is found for "current_user/workplace" look for a value
115 for "current_user" regardless of workspace. The corresponding concept is:
116
117 "Did *I* set this option anywhere on this site ? If so, reuse the value."
118
119 'workplace': When no value is found for "current_user/workplace" look for a value
120 for "workplace" regardless of user. The corresponding concept is:
121
122 "Did anyone set this option for *this workplace* ? If so, reuse that value."
123
124 @param default: if no value is found for the option this value is returned
125 instead, also the option is set to this value in the backend, if <None>
126 a missing option will NOT be created in the backend
127 @param sql_return_type: a PostgreSQL type the value of the option is to be
128 cast to before returning, if None no cast will be applied, you will
129 want to make sure that sql_return_type and type(default) are compatible
130 """
131 if None in [option, workplace]:
132 raise ValueError, 'neither <option> (%s) nor <workplace> (%s) may be [None]' % (option, workplace)
133 if bias not in ['user', 'workplace']:
134 raise ValueError, '<bias> must be "user" or "workplace"'
135
136
137 cmd = u"select type from cfg.cfg_template where name=%(opt)s"
138 rows, idx = gmPG2.run_ro_queries(link_obj=self.ro_conn, queries = [{'cmd': cmd, 'args': {'opt': option}}])
139 if len(rows) == 0:
140
141 if default is None:
142
143 return None
144 _log.info('creating option [%s] with default [%s]' % (option, default))
145 success = self.set(workplace = workplace, cookie = cookie, option = option, value = default)
146 if not success:
147
148 _log.error('creating option failed')
149 return default
150
151 cfg_table_type_suffix = rows[0][0]
152 args = {
153 'opt': option,
154 'wp': workplace,
155 'cookie': cookie,
156 'def': cfg_DEFAULT
157 }
158
159 if cfg_table_type_suffix == u'data':
160 sql_return_type = u''
161 else:
162 sql_return_type = gmTools.coalesce (
163 initial = sql_return_type,
164 instead = u'',
165 template_initial = u'::%s'
166 )
167
168
169 where_parts = [
170 u'vco.owner = CURRENT_USER',
171 u'vco.workplace = %(wp)s',
172 u'vco.option = %(opt)s'
173 ]
174 where_parts.append(gmTools.coalesce (
175 initial = cookie,
176 instead = u'vco.cookie is null',
177 template_initial = u'vco.cookie = %(cookie)s'
178 ))
179 cmd = u"select vco.value%s from cfg.v_cfg_opts_%s vco where %s limit 1" % (
180 sql_return_type,
181 cfg_table_type_suffix,
182 u' and '.join(where_parts)
183 )
184 rows, idx = gmPG2.run_ro_queries(link_obj=self.ro_conn, queries = [{'cmd': cmd, 'args': args}])
185 if len(rows) > 0:
186 if cfg_table_type_suffix == u'data':
187 return cPickle.loads(str(rows[0][0]))
188 return rows[0][0]
189
190 _log.warning('no user AND workplace specific value for option [%s] in config database' % option)
191
192
193 if bias == 'user':
194
195 where_parts = [
196 u'vco.option = %(opt)s',
197 u'vco.owner = CURRENT_USER',
198 ]
199 else:
200
201 where_parts = [
202 u'vco.option = %(opt)s',
203 u'vco.workplace = %(wp)s'
204 ]
205 where_parts.append(gmTools.coalesce (
206 initial = cookie,
207 instead = u'vco.cookie is null',
208 template_initial = u'vco.cookie = %(cookie)s'
209 ))
210 cmd = u"select vco.value%s from cfg.v_cfg_opts_%s vco where %s" % (
211 sql_return_type,
212 cfg_table_type_suffix,
213 u' and '.join(where_parts)
214 )
215 rows, idx = gmPG2.run_ro_queries(link_obj=self.ro_conn, queries = [{'cmd': cmd, 'args': args}])
216 if len(rows) > 0:
217
218 self.set (
219 workplace = workplace,
220 cookie = cookie,
221 option = option,
222 value = rows[0][0]
223 )
224 if cfg_table_type_suffix == u'data':
225 return cPickle.loads(str(rows[0][0]))
226 return rows[0][0]
227
228 _log.warning('no user OR workplace specific value for option [%s] in config database' % option)
229
230
231 where_parts = [
232 u'vco.owner = %(def)s',
233 u'vco.workplace = %(def)s',
234 u'vco.option = %(opt)s'
235 ]
236 cmd = u"select vco.value%s from cfg.v_cfg_opts_%s vco where %s" % (
237 sql_return_type,
238 cfg_table_type_suffix,
239 u' and '.join(where_parts)
240 )
241 rows, idx = gmPG2.run_ro_queries(link_obj=self.ro_conn, queries = [{'cmd': cmd, 'args': args}])
242 if len(rows) > 0:
243
244 self.set (
245 workplace = workplace,
246 cookie = cookie,
247 option = option,
248 value = rows[0]['value']
249 )
250 if cfg_table_type_suffix == u'data':
251 return cPickle.loads(str(rows[0]['value']))
252 return rows[0]['value']
253
254 _log.warning('no default site policy value for option [%s] in config database' % option)
255
256
257 if default is None:
258 _log.warning('no default value for option [%s] supplied by caller' % option)
259 return None
260 _log.info('setting option [%s] to default [%s]' % (option, default))
261 success = self.set (
262 workplace = workplace,
263 cookie = cookie,
264 option = option,
265 value = default
266 )
267 if not success:
268 return None
269
270 return default
271
272 - def getID(self, workplace = None, cookie = None, option = None):
273 """Get config value from database.
274
275 - unset arguments are assumed to mean database defaults except for <cookie>
276 """
277
278 if option is None:
279 _log.error("Need to know which option to retrieve.")
280 return None
281
282 alias = self.__make_alias(workplace, 'CURRENT_USER', cookie, option)
283
284
285 where_parts = [
286 'vco.option=%(opt)s',
287 'vco.workplace=%(wplace)s'
288 ]
289 where_args = {
290 'opt': option,
291 'wplace': workplace
292 }
293 if workplace is None:
294 where_args['wplace'] = cfg_DEFAULT
295
296 where_parts.append('vco.owner=CURRENT_USER')
297
298 if cookie is not None:
299 where_parts.append('vco.cookie=%(cookie)s')
300 where_args['cookie'] = cookie
301 where_clause = ' and '.join(where_parts)
302 cmd = u"""
303 select vco.pk_cfg_item
304 from cfg.v_cfg_options vco
305 where %s
306 limit 1""" % where_clause
307
308 rows, idx = gmPG2.run_ro_queries(link_obj=self.ro_conn, queries = [{'cmd': cmd, 'args': where_args}], return_data=True)
309 if len(rows) == 0:
310 _log.warning('option definition for [%s] not in config database' % alias)
311 return None
312 return rows[0][0]
313
314 - def set(self, workplace = None, cookie = None, option = None, value = None):
315 """Set (insert or update) option value in database.
316
317 Any parameter that is None will be set to the database default.
318
319 Note: you can't change the type of a parameter once it has been
320 created in the backend. If you want to change the type you will
321 have to delete the parameter and recreate it using the new type.
322 """
323
324 if None in [option, value]:
325 raise ValueError('invalid arguments (option=<%s>, value=<%s>)' % (option, value))
326
327 rw_conn = gmPG2.get_connection(readonly=False)
328
329 alias = self.__make_alias(workplace, 'CURRENT_USER', cookie, option)
330
331 opt_value = value
332 sql_type_cast = u''
333 if isinstance(value, basestring):
334 sql_type_cast = u'::text'
335 elif isinstance(value, types.BooleanType):
336 opt_value = int(opt_value)
337 elif isinstance(value, (types.FloatType, types.IntType, types.LongType, decimal.Decimal, types.BooleanType)):
338 sql_type_cast = u'::numeric'
339 elif isinstance(value, types.ListType):
340
341 pass
342 elif isinstance(value, types.BufferType):
343
344 pass
345 else:
346 try:
347 opt_value = gmPG2.dbapi.Binary(cPickle.dumps(value))
348 sql_type_cast = '::bytea'
349 except cPickle.PicklingError:
350 _log.error("cannot pickle option of type [%s] (key: %s, value: %s)", type(value), alias, str(value))
351 raise
352 except:
353 _log.error("don't know how to store option of type [%s] (key: %s, value: %s)", type(value), alias, str(value))
354 raise
355
356 cmd = u'select cfg.set_option(%%(opt)s, %%(val)s%s, %%(wp)s, %%(cookie)s, NULL)' % sql_type_cast
357 args = {
358 'opt': option,
359 'val': opt_value,
360 'wp': workplace,
361 'cookie': cookie
362 }
363 try:
364 rows, idx = gmPG2.run_rw_queries(link_obj=rw_conn, queries=[{'cmd': cmd, 'args': args}], return_data=True)
365 result = rows[0][0]
366 except:
367 _log.exception('cannot set option')
368 result = False
369
370 rw_conn.commit()
371 rw_conn.close()
372
373 return result
374
376 """Get names of all stored parameters for a given workplace/(user)/cookie-key.
377 This will be used by the ConfigEditor object to create a parameter tree.
378 """
379
380 where_snippets = [
381 u'cfg_template.pk=cfg_item.fk_template',
382 u'cfg_item.workplace=%(wplace)s'
383 ]
384 where_args = {'wplace': workplace}
385
386
387 if user is None:
388 where_snippets.append(u'cfg_item.owner=CURRENT_USER')
389 else:
390 where_snippets.append(u'cfg_item.owner=%(usr)s')
391 where_args['usr'] = user
392
393 where_clause = u' and '.join(where_snippets)
394
395 cmd = u"""
396 select name, cookie, owner, type, description
397 from cfg.cfg_template, cfg.cfg_item
398 where %s""" % where_clause
399
400
401 rows, idx = gmPG2.run_ro_queries(link_obj=self.ro_conn, queries = [{'cmd': cmd, 'args': where_args}], return_data=True)
402 return rows
403
404 - def delete(self, workplace = None, cookie = None, option = None):
405 """
406 Deletes an option or a whole group.
407 Note you have to call store() in order to save
408 the changes.
409 """
410 if option is None:
411 raise ValueError('<option> cannot be None')
412
413 if cookie is None:
414 cmd = u"""
415 delete from cfg.cfg_item where
416 fk_template=(select pk from cfg.cfg_template where name = %(opt)s) and
417 owner = CURRENT_USER and
418 workplace = %(wp)s and
419 cookie is Null
420 """
421 else:
422 cmd = u"""
423 delete from cfg.cfg_item where
424 fk_template=(select pk from cfg.cfg_template where name = %(opt)s) and
425 owner = CURRENT_USER and
426 workplace = %(wp)s and
427 cookie = %(cookie)s
428 """
429 args = {'opt': option, 'wp': workplace, 'cookie': cookie}
430 gmPG2.run_rw_queries(queries=[{'cmd': cmd, 'args': args}])
431 return True
432
434 return '%s-%s-%s-%s' % (workplace, user, cookie, option)
435
436 -def getDBParam(workplace = None, cookie = None, option = None):
437 """Convenience function to get config value from database.
438
439 will search for context dependant match in this order:
440 - CURRENT_USER_CURRENT_WORKPLACE
441 - CURRENT_USER_DEFAULT_WORKPLACE
442 - DEFAULT_USER_CURRENT_WORKPLACE
443 - DEFAULT_USER_DEFAULT_WORKPLACE
444
445 We assume that the config tables are found on service "default".
446 That way we can handle the db connection inside this function.
447
448 Returns (value, set) of first match.
449 """
450
451
452
453 if option is None:
454 return (None, None)
455
456
457 dbcfg = cCfgSQL()
458
459
460 sets2search = []
461 if workplace is not None:
462 sets2search.append(['CURRENT_USER_CURRENT_WORKPLACE', None, workplace])
463 sets2search.append(['CURRENT_USER_DEFAULT_WORKPLACE', None, None])
464 if workplace is not None:
465 sets2search.append(['DEFAULT_USER_CURRENT_WORKPLACE', cfg_DEFAULT, workplace])
466 sets2search.append(['DEFAULT_USER_DEFAULT_WORKPLACE', cfg_DEFAULT, None])
467
468 matchingSet = None
469 result = None
470 for set in sets2search:
471 result = dbcfg.get(
472 workplace = set[2],
473 user = set[1],
474 option = option,
475 cookie = cookie
476 )
477 if result is not None:
478 matchingSet = set[0]
479 break
480 _log.debug('[%s] not found for [%s@%s]' % (option, set[1], set[2]))
481
482
483 if matchingSet is None:
484 _log.warning('no config data for [%s]' % option)
485 return (result, matchingSet)
486
487 -def setDBParam(workplace = None, user = None, cookie = None, option = None, value = None):
488 """Convenience function to store config values in database.
489
490 We assume that the config tables are found on service "default".
491 That way we can handle the db connection inside this function.
492
493 Omitting any parameter (or setting to None) will store database defaults for it.
494
495 - returns True/False
496 """
497
498 dbcfg = cCfgSQL()
499
500 success = dbcfg.set(
501 workplace = workplace,
502 user = user,
503 option = option,
504 value = value
505 )
506
507 if not success:
508 return False
509 return True
510
511
512
513 if __name__ == "__main__":
514
515 if len(sys.argv) < 2:
516 sys.exit()
517
518 if sys.argv[1] != 'test':
519 sys.exit()
520
521 root = logging.getLogger()
522 root.setLevel(logging.DEBUG)
523
525 for opt in get_all_options():
526 print u'%s (%s): %s (%s@%s)' % (opt['option'], opt['type'], opt['value'], opt['owner'], opt['workplace'])
527
528
529
531 print "testing database config"
532 print "======================="
533
534 myDBCfg = cCfgSQL()
535
536 print "delete() works:", myDBCfg.delete(option='font name', workplace = 'test workplace')
537 print "font is initially:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user')
538 print "set() works:", myDBCfg.set(option='font name', value="Times New Roman", workplace = 'test workplace')
539 print "font after set():", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user')
540 print "delete() works:", myDBCfg.delete(option='font name', workplace = 'test workplace')
541 print "font after delete():", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user')
542 print "font after get() with default:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user', default = 'WingDings')
543 print "font right after get() with another default:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user', default = 'default: Courier')
544 print "set() works:", myDBCfg.set(option='font name', value="Times New Roman", workplace = 'test workplace')
545 print "font after set() on existing option:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user')
546
547 print "setting array option"
548 print "array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user')
549 aList = ['val 1', 'val 2']
550 print "set():", myDBCfg.set(option='test array', value = aList, workplace = 'test workplace')
551 print "array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user')
552 aList = ['val 11', 'val 12']
553 print "set():", myDBCfg.set(option='test array', value = aList, workplace = 'test workplace')
554 print "array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user')
555 print "delete() works:", myDBCfg.delete(option='test array', workplace='test workplace')
556 print "array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user')
557
558 print "setting complex option"
559 data = {1: 'line 1', 2: 'line2', 3: {1: 'line3.1', 2: 'line3.2'}, 4: 1234}
560 print "set():", myDBCfg.set(option = "complex option test", value = data, workplace = 'test workplace')
561 print "complex option now:", myDBCfg.get2(workplace = 'test workplace', option = "complex option test", bias = 'user')
562 print "delete() works:", myDBCfg.delete(option = "complex option test", workplace = 'test workplace')
563 print "complex option now:", myDBCfg.get2(workplace = 'test workplace', option = "complex option test", bias = 'user')
564
565
566 test_get_all_options()
567
568
569
570
571
572
573
574