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