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(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 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 # FIXME: make a cBorg around this
93 -class cCfgSQL:
94 - def __init__(self):
95 self.ro_conn = gmPG2.get_connection()
96 #----------------------------------------------- 97 # external API 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 # does this option exist ? 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 # not found ... 141 if default is None: 142 # ... and no default either 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 # ... but cannot create option with default value either 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 # 1) search value with explicit workplace and current user 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 # 2) search value with biased query 193 if bias == 'user': 194 # did *I* set this option on *any* workplace ? 195 where_parts = [ 196 u'vco.option = %(opt)s', 197 u'vco.owner = CURRENT_USER', 198 ] 199 else: 200 # did *anyone* set this option on *this* workplace ? 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 # set explicitely for user/workplace 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 # 3) search value within default site policy 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 # set explicitely for user/workplace 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 # 4) not found, set default ? 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 # sanity checks 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 # construct query 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 # sanity checks 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 # there can be different syntaxes for list types so don't try to cast them 341 pass 342 elif isinstance(value, types.BufferType): 343 # can go directly into bytea 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() # will rollback if transaction failed 371 rw_conn.close() 372 373 return result
374 #-------------------------------------------
375 - def getAllParams(self, user = None, workplace = cfg_DEFAULT):
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 # if no workplace given: any workplace (= cfg_DEFAULT) 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 # if no user given: current db user 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 # retrieve option definition 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 #----------------------------
433 - def __make_alias(self, workplace, user, cookie, option):
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 # FIXME: depending on set store for user ... 452 453 if option is None: 454 return (None, None) 455 456 # connect to database (imports gmPG2 if need be) 457 dbcfg = cCfgSQL() 458 459 # (set_name, user, workplace) 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 # loop over sets 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 # cleanup 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 # connect to database 498 dbcfg = cCfgSQL() 499 # set value 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 # main 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 #---------------------------------------------------------
524 - def test_get_all_options():
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 # print u' %s' % opt['description'] 528 # print u' %s on %s' % (opt['owner'], opt['workplace']) 529 #---------------------------------------------------------
530 - def test_db_cfg():
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 # try: 568 # test_db_cfg() 569 # except: 570 # _log.exception('test suite failed') 571 # raise 572 573 #============================================================= 574