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