Gnumed.pycommon.gmCfg

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, pickle, 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 = ''
     47          else:
     48                  order_by = 'ORDER BY %s' % order_by
     49
     50          cmd = """
     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
     97  #       def __init__(self):
     98  #               pass
     99
    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 = "select type from cfg.cfg_template where name=%(opt)s"
    142                  rows, idx = gmPG2.run_ro_queries(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 == 'data':
    164                          sql_return_type = ''
    165                  else:
    166                          sql_return_type = gmTools.coalesce (
    167                                  value2test = sql_return_type,
    168                                  return_instead = '',
    169                                  template4value = '::%s'
    170                          )
    171
    172                  # 1) search value with explicit workplace and current user
    173                  where_parts = [
    174                          'vco.owner = CURRENT_USER',
    175                          'vco.workplace = %(wp)s',
    176                          'vco.option = %(opt)s'
    177                  ]
    178                  where_parts.append(gmTools.coalesce (
    179                          value2test = cookie,
    180                          return_instead = 'vco.cookie is null',
    181                          template4value = 'vco.cookie = %(cookie)s'
    182                  ))
    183                  cmd = "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                          ' and '.join(where_parts)
    187                  )
    188                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}])
    189                  if len(rows) > 0:
    190                          if cfg_table_type_suffix == 'data':
    191                                  return pickle.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                                  'vco.option = %(opt)s',
    201                                  'vco.owner = CURRENT_USER',
    202                          ]
    203                  else:
    204                          # did *anyone* set this option on *this* workplace ?
    205                          where_parts = [
    206                                  'vco.option = %(opt)s',
    207                                  'vco.workplace = %(wp)s'
    208                          ]
    209                  where_parts.append(gmTools.coalesce (
    210                          value2test = cookie,
    211                          return_instead = 'vco.cookie is null',
    212                          template4value = 'vco.cookie = %(cookie)s'
    213                  ))
    214                  cmd = "select vco.value%s from cfg.v_cfg_opts_%s vco where %s" % (
    215                          sql_return_type,
    216                          cfg_table_type_suffix,
    217                          ' and '.join(where_parts)
    218                  )
    219                  rows, idx = gmPG2.run_ro_queries(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 == 'data':
    229                                  return pickle.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                          'vco.owner = %(def)s',
    237                          'vco.workplace = %(def)s',
    238                          'vco.option = %(opt)s'
    239                  ]
    240                  cmd = "select vco.value%s from cfg.v_cfg_opts_%s vco where %s" % (
    241                          sql_return_type,
    242                          cfg_table_type_suffix,
    243                          ' and '.join(where_parts)
    244                  )
    245                  rows, idx = gmPG2.run_ro_queries(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 == 'data':
    255                                  return pickle.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 = """
    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(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 = ''
    337                  if isinstance(value, str):
    338                          sql_type_cast = '::text'
    339                  elif isinstance(value, bool):
    340                          opt_value = int(opt_value)
    341                  elif isinstance(value, (float, int, decimal.Decimal, bool)):
    342                          sql_type_cast = '::numeric'
    343                  elif isinstance(value, list):
    344                          # there can be different syntaxes for list types so don't try to cast them
    345                          pass
    346                  elif isinstance(value, buffer):
    347                          # can go directly into bytea
    348                          pass
    349                  else:
    350                          opt_value = gmPG2.dbapi.Binary(pickle.dumps(value))
    351                          sql_type_cast = '::bytea'
    352
    353                  cmd = 'select cfg.set_option(%%(opt)s, %%(val)s%s, %%(wp)s, %%(cookie)s, NULL)' % sql_type_cast
    354                  args = {
    355                          'opt': option,
    356                          'val': opt_value,
    357                          'wp': workplace,
    358                          'cookie': cookie
    359                  }
    360                  try:
    361                          rows, idx = gmPG2.run_rw_queries(link_obj=rw_conn, queries=[{'cmd': cmd, 'args': args}], return_data=True)
    362                          result = rows[0][0]
    363                  except Exception:
    364                          _log.exception('cannot set option')
    365                          result = False
    366
    367                  rw_conn.commit()                # will rollback if transaction failed
    368                  rw_conn.close()
    369
    370                  return result
    371
    372          #-------------------------------------------
    373 -        def getAllParams(self, user = None, workplace = cfg_DEFAULT):
    374                  """Get names of all stored parameters for a given workplace/(user)/cookie-key.
    375                  This will be used by the ConfigEditor object to create a parameter tree.
    376                  """
    377                  # if no workplace given: any workplace (= cfg_DEFAULT)
    378                  where_snippets = [
    379                          'cfg_template.pk=cfg_item.fk_template',
    380                          'cfg_item.workplace=%(wplace)s'
    381                  ]
    382                  where_args = {'wplace': workplace}
    383
    384                  # if no user given: current db user
    385                  if user is None:
    386                          where_snippets.append('cfg_item.owner=CURRENT_USER')
    387                  else:
    388                          where_snippets.append('cfg_item.owner=%(usr)s')
    389                          where_args['usr'] = user
    390
    391                  where_clause = ' and '.join(where_snippets)
    392
    393                  cmd = """
    394  select name, cookie, owner, type, description
    395  from cfg.cfg_template, cfg.cfg_item
    396  where %s""" % where_clause
    397
    398                  # retrieve option definition
    399                  rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': where_args}], return_data=True)
    400                  return rows
    401
    402          #----------------------------
    403 -        def delete(self, conn=None, pk_option=None):
    404                  if conn is None:
    405                          # without a gm-dbo connection you can only delete your own options :-)
    406                          cmd = "DELETE FROM cfg.cfg_item WHERE pk = %(pk)s AND owner = CURRENT_USER"
    407                  else:
    408                          cmd = "DELETE FROM cfg.cfg_item WHERE pk = %(pk)s"
    409                  args = {'pk': pk_option}
    410                  gmPG2.run_rw_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}], end_tx = True)
    411          #----------------------------
    412 -        def delete_old(self, workplace = None, cookie = None, option = None):
    413                  """
    414                  Deletes an option or a whole group.
    415                  Note you have to call store() in order to save
    416                  the changes.
    417                  """
    418                  if option is None:
    419                          raise ValueError('<option> cannot be None')
    420
    421                  if cookie is None:
    422                          cmd = """
    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 is Null
    428  """
    429                  else:
    430                          cmd = """
    431  delete from cfg.cfg_item where
    432          fk_template=(select pk from cfg.cfg_template where name = %(opt)s) and
    433          owner = CURRENT_USER and
    434          workplace = %(wp)s and
    435          cookie = %(cookie)s
    436  """
    437                  args = {'opt': option, 'wp': workplace, 'cookie': cookie}
    438                  gmPG2.run_rw_queries(queries=[{'cmd': cmd, 'args': args}])
    439                  return True
    440          #----------------------------
    441 -        def __make_alias(self, workplace, user, cookie, option):
    442                  return '%s-%s-%s-%s' % (workplace, user, cookie, option)
    443  #===================================================================
    444 -def getDBParam(workplace = None, cookie = None, option = None):
    445          """Convenience function to get config value from database.
    446
    447          will search for context dependant match in this order:
    448                  - CURRENT_USER_CURRENT_WORKPLACE
    449                  - CURRENT_USER_DEFAULT_WORKPLACE
    450                  - DEFAULT_USER_CURRENT_WORKPLACE
    451                  - DEFAULT_USER_DEFAULT_WORKPLACE
    452
    453          We assume that the config tables are found on service "default".
    454          That way we can handle the db connection inside this function.
    455
    456          Returns (value, set) of first match.
    457          """
    458
    459          # FIXME: depending on set store for user ...
    460
    461          if option is None:
    462                  return (None, None)
    463
    464          # connect to database (imports gmPG2 if need be)
    465          dbcfg = cCfgSQL()
    466
    467          # (set_name, user, workplace)
    468          sets2search = []
    469          if workplace is not None:
    470                  sets2search.append(['CURRENT_USER_CURRENT_WORKPLACE', None, workplace])
    471          sets2search.append(['CURRENT_USER_DEFAULT_WORKPLACE', None, None])
    472          if workplace is not None:
    473                  sets2search.append(['DEFAULT_USER_CURRENT_WORKPLACE', cfg_DEFAULT, workplace])
    474          sets2search.append(['DEFAULT_USER_DEFAULT_WORKPLACE', cfg_DEFAULT, None])
    475          # loop over sets
    476          matchingSet = None
    477          result = None
    478          for set in sets2search:
    479                  result = dbcfg.get(
    480                          workplace = set[2],
    481                          user = set[1],
    482                          option = option,
    483                          cookie = cookie
    484                  )
    485                  if result is not None:
    486                          matchingSet = set[0]
    487                          break
    488                  _log.debug('[%s] not found for [%s@%s]' % (option, set[1], set[2]))
    489
    490          # cleanup
    491          if matchingSet is None:
    492                  _log.warning('no config data for [%s]' % option)
    493          return (result, matchingSet)
    494  #-------------------------------------------------------------
    495 -def setDBParam(workplace = None, user = None, cookie = None, option = None, value = None):
    496          """Convenience function to store config values in database.
    497
    498          We assume that the config tables are found on service "default".
    499          That way we can handle the db connection inside this function.
    500
    501          Omitting any parameter (or setting to None) will store database defaults for it.
    502
    503          - returns True/False
    504          """
    505          # connect to database
    506          dbcfg = cCfgSQL()
    507          # set value
    508          success = dbcfg.set(
    509                  workplace = workplace,
    510                  user = user,
    511                  option = option,
    512                  value = value
    513          )
    514
    515          if not success:
    516                  return False
    517          return True
    518  #=============================================================
    519  # main
    520  #=============================================================
    521  if __name__ == "__main__":
    522
    523          if len(sys.argv) < 2:
    524                  sys.exit()
    525
    526          if sys.argv[1] != 'test':
    527                  sys.exit()
    528
    529          root = logging.getLogger()
    530          root.setLevel(logging.DEBUG)
    531          #---------------------------------------------------------
    532 -        def test_get_all_options():
    533                  for opt in get_all_options():
    534                          print('%s (%s): %s (%s@%s)' % (opt['option'], opt['type'], opt['value'], opt['owner'], opt['workplace']))
    535  #                       print(u' %s' % opt['description'])
    536  #                       print(u' %s on %s' % (opt['owner'], opt['workplace']))
    537          #---------------------------------------------------------
    538 -        def test_db_cfg():
    539                  print("testing database config")
    540                  print("=======================")
    541
    542                  myDBCfg = cCfgSQL()
    543
    544                  print("delete() works:", myDBCfg.delete(option='font name', workplace = 'test workplace'))
    545                  print("font is initially:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user'))
    546                  print("set() works:", myDBCfg.set(option='font name', value="Times New Roman", workplace = 'test workplace'))
    547                  print("font after set():", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user'))
    548                  print("delete() works:", myDBCfg.delete(option='font name', workplace = 'test workplace'))
    549                  print("font after delete():", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user'))
    550                  print("font after get() with default:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user', default = 'WingDings'))
    551                  print("font right after get() with another default:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user', default = 'default: Courier'))
    552                  print("set() works:", myDBCfg.set(option='font name', value="Times New Roman", workplace = 'test workplace'))
    553                  print("font after set() on existing option:", myDBCfg.get2(option = 'font name', workplace = 'test workplace', bias = 'user'))
    554
    555                  print("setting array option")
    556                  print("array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user'))
    557                  aList = ['val 1', 'val 2']
    558                  print("set():", myDBCfg.set(option='test array', value = aList, workplace = 'test workplace'))
    559                  print("array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user'))
    560                  aList = ['val 11', 'val 12']
    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                  print("delete() works:", myDBCfg.delete(option='test array', workplace='test workplace'))
    564                  print("array now:", myDBCfg.get2(option = 'test array', workplace = 'test workplace', bias = 'user'))
    565
    566                  print("setting complex option")
    567                  data = {1: 'line 1', 2: 'line2', 3: {1: 'line3.1', 2: 'line3.2'}, 4: 1234}
    568                  print("set():", myDBCfg.set(option = "complex option test", value = data, workplace = 'test workplace'))
    569                  print("complex option now:", myDBCfg.get2(workplace = 'test workplace', option = "complex option test", bias = 'user'))
    570                  print("delete() works:", myDBCfg.delete(option = "complex option test", workplace = 'test workplace'))
    571                  print("complex option now:", myDBCfg.get2(workplace = 'test workplace', option = "complex option test", bias = 'user'))
    572
    573          #---------------------------------------------------------
    574          test_get_all_options()
    575  #               test_db_cfg()
    576
    577  #=============================================================
    578