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