Package Gnumed :: Package pycommon :: Module gmCfg
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmCfg

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