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 
 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():
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 # FIXME: make a cBorg around this
88 -class cCfgSQL:
89 - def __init__(self):
90 self.ro_conn = gmPG2.get_connection()
91 #----------------------------------------------- 92 # external API 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 # does this option exist ? 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 # not found ... 136 if default is None: 137 # ... and no default either 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 # ... but cannot create option with default value either 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 # 1) search value with explicit workplace and current user 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 # 2) search value with biased query 188 if bias == 'user': 189 # did *I* set this option on *any* workplace ? 190 where_parts = [ 191 u'vco.option = %(opt)s', 192 u'vco.owner = CURRENT_USER', 193 ] 194 else: 195 # did *anyone* set this option on *this* workplace ? 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 # set explicitely for user/workplace 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 # 3) search value within default site policy 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 # set explicitely for user/workplace 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 # 4) not found, set default ? 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 # sanity checks 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 # construct query 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 # sanity checks 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 # there can be different syntaxes for list types so don't try to cast them 336 pass 337 elif isinstance(value, types.BufferType): 338 # can go directly into bytea 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() # will rollback if transaction failed 366 rw_conn.close() 367 368 return result
369 #-------------------------------------------
370 - def getAllParams(self, user = None, workplace = cfg_DEFAULT):
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 # if no workplace given: any workplace (= cfg_DEFAULT) 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 # if no user given: current db user 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 # retrieve option definition 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 #----------------------------
428 - def __make_alias(self, workplace, user, cookie, option):
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 # FIXME: depending on set store for user ... 447 448 if option is None: 449 return (None, None) 450 451 # connect to database (imports gmPG2 if need be) 452 dbcfg = cCfgSQL() 453 454 # (set_name, user, workplace) 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 # loop over sets 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 # cleanup 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 # connect to database 493 dbcfg = cCfgSQL() 494 # set value 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 # main 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 #---------------------------------------------------------
519 - def test_get_all_options():
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 # print u' %s' % opt['description'] 523 # print u' %s on %s' % (opt['owner'], opt['workplace']) 524 #---------------------------------------------------------
525 - def test_db_cfg():
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 # try: 563 # test_db_cfg() 564 # except: 565 # _log.exception('test suite failed') 566 # raise 567 568 #============================================================= 569