| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

In This Package:

dbconf.py

Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 """
00003 When invoked as a script determines if the 
00004 configuration named in the single argument exists.
00005 
00006 Usage example::
00007  
00008   python path/to/dbconf.py configname  && echo configname exists || echo no configname
00009 
00010 """
00011 
00012 import os
00013 class DBConf(dict):
00014     """
00015     Reads a section of the Database configuration file, 
00016     storing key/value pairs into this dict. The default file *path* is ``~/.my.cnf``
00017     which is formatted like::
00018 
00019        [testdb]
00020        host      = dybdb1.ihep.ac.cn
00021        database  = testdb
00022        user      = dayabay
00023        password  = youknowoit
00024 
00025     The standard python :py:mod:`ConfigParser` is used, 
00026     which supports ``%(name)s`` style replacements in other values. 
00027  
00028     Usage example::
00029 
00030        from DybPython import DBConf
00031        dbc = DBConf(sect="client", path="~/.my.cnf" ) 
00032        print dbc['host']
00033 
00034        dbo = DBConf("offline_db")
00035        assert dbo['host'] == "dybdb1.ihep.ac.cn"
00036 
00037     .. warning::
00038          As passwords are contained **DO NOT COMMIT** into any repository, and protect the file.
00039 
00040     """
00041     defaults = { 
00042                  'path':"$SITEROOT/../.my.cnf:~/.my.cnf", 
00043                  'sect':"offline_db",
00044                  'host':"%(host)s", 
00045                  'user':"%(user)s", 
00046                    'db':"%(database)s", 
00047                  'pswd':"%(password)s",
00048                   'url':"mysql://%(host)s/%(database)s", 
00049                   'fix':None,
00050                   'fixpass':None,
00051                   'restrict':None,
00052                }
00053 
00054     def Export(cls, sect=None , **extras ):
00055         """
00056         Exports the environment settings into environment of python process 
00057         this is invoked by the C++ *DbiCascader* ctor 
00058         """
00059         cnf = DBConf( sect=sect )  
00060         if cnf.fix == None:
00061             cnf.export_to_env(**extras)
00062         else:
00063             from dbcas import DBCas
00064             cas = DBCas(cnf)
00065             tas = cas.spawn()
00066             cnf.export_to_env( supplier=tas )                
00067         return cnf
00068         #print dbc.dump_env()
00069     Export = classmethod( Export )
00070 
00071     def __init__(self, sect=None , path=None , user=None, pswd=None, url=None , host=None, db=None , fix=None, fixpass=None, restrict=None, verbose=False, secure=False, from_env=False , nodb=False ): 
00072         """
00073 
00074         See also :ref:`dbi:running` section of the Offline User Manual 
00075 
00076         Interpolates the DB connection parameter patterns gleaned 
00077         from arguments, envvars or defaults (in that precedence order)
00078         into usable values using the context supplied by the 
00079         *sect* section of the ini format config file at *path*
00080 
00081         Optional keyword arguments:
00082      
00083                ================   =======================================================
00084                Keyword            Description
00085                ================   =======================================================
00086                   *sect*            section in config file 
00087                   *path*            colon delimited list of paths to config file
00088 
00089                   *user*            username 
00090                   *pswd*            password
00091                   *url*             connection url
00092                   *host*            db host 
00093                   *db*              db name
00094 
00095                   *fix*             triggers fixture loading into temporary 
00096                                     spawned cascade and specifies paths to fixture files
00097                                     for each member of the cascade (semi-colon delimited)  
00098                   *fixpass*         skip the DB cascade  dropping/creation that is 
00099                                     normally done
00100                                     as part of cascade spawning (used in DBWriter/tests) 
00101                   *restrict*        constrain the names of DB that can connect to 
00102                                     starting with a string, eg ``tmp_`` as a safeguard
00103                   *nodb*            used to connect without specifying the database
00104                                     this requires greater access privileges and is used
00105                                     to perform database dropping/creation
00106                ================   =======================================================
00107 
00108                                     
00109         Correspondingly named envvars can also be used:
00110 
00111                 .. envvar:: DBCONF 
00112                 .. envvar:: DBCONF_PATH  
00113 
00114                 .. envvar:: DBCONF_USER
00115                 .. envvar:: DBCONF_PWSD
00116                 .. envvar:: DBCONF_URL
00117                 .. envvar:: DBCONF_HOST
00118                 .. envvar:: DBCONF_DB
00119 
00120                 .. envvar:: DBCONF_FIX
00121                 .. envvar:: DBCONF_FIXPASS
00122                 .. envvar:: DBCONF_RESTRICT
00123 
00124         The :envvar:`DBCONF` existance also triggers the 
00125         :meth:`DybPython.dbconf.DBConf.Export` in :dybgaudi:`Database/DatabaseInterface/src/DbiCascader.cxx` 
00126 
00127         The :envvar:`DBCONF_PATH` is a colon delimited list of paths that are 
00128         user (~) and $envvar OR ${envvar} expanded, some of the paths 
00129         may not exist.  When there are repeated settings in more than one
00130         file the last one wins.
00131 
00132         In secure mode a single protected config file is required, the security 
00133         comes with a high price in convenience
00134 
00135         """
00136 
00137         self.secure = secure
00138         self.verbose = verbose
00139 
00140         esect = os.environ.get('DBCONF', None )
00141         if esect == "" or esect == None:
00142             esect = DBConf.defaults['sect']
00143 
00144         sect   = sect    or esect
00145         path   = path    or os.environ.get('DBCONF_PATH', DBConf.defaults['path'] ) 
00146 
00147         user   = user    or os.environ.get('DBCONF_USER', DBConf.defaults['user'] ) 
00148         pswd   = pswd    or os.environ.get('DBCONF_PSWD', DBConf.defaults['pswd'] ) 
00149         url    = url     or os.environ.get('DBCONF_URL',  DBConf.defaults['url'] ) 
00150         host   = host    or os.environ.get('DBCONF_HOST', DBConf.defaults['host'] ) 
00151         db     = db      or os.environ.get('DBCONF_DB'  , DBConf.defaults['db'] ) 
00152 
00153         fix    = fix     or os.environ.get('DBCONF_FIX' , DBConf.defaults['fix'] ) 
00154         fixpass = fixpass or os.environ.get('DBCONF_FIXPASS' , DBConf.defaults['fixpass'] ) 
00155         restrict = restrict or os.environ.get('DBCONF_RESTRICT' , DBConf.defaults['restrict'] ) 
00156  
00157         if self.secure:
00158             self._check_path( path )
00159         if not from_env:
00160             user,pswd,host,db,url = self.configure_cascade( sect, path ) 
00161  
00162         if restrict:
00163             dbns = db.split(";")
00164             dbok = filter( lambda _:_.startswith(restrict) , dbns )
00165             assert len(dbns) == len(dbok), "DBCONF_RESTRICTion on DB names violated : all DB names must be prefixed with \"%s\" DB names :\"%s\"  DB ok names: \"%s\"  " % ( restrict , dbns , dbok  )
00166 
00167 
00168         fsect = sect.split(":")[0]   ## first or only 
00169 
00170         self.sect = sect
00171         self.path = path
00172         self.user = user
00173         self.pswd = pswd
00174         self.url  = url
00175         self.host = host
00176         self.db   = db
00177         self.fix  = fix
00178         self.fixpass  = fixpass
00179         self.nodb = nodb
00180 
00181     def mysqldb_parameters(self, nodb=False):
00182         """
00183         Using the `nodb=True` option skips database name parameter, this is useful
00184         when creating or dropping a database
00185         """
00186         #return dict(read_default_file=self.path, read_default_group=self.sect)
00187         d = dict(host=self.host % self, user=self.user % self, passwd=self.pswd % self ) 
00188         if not nodb:
00189             d.update( db=self.db % self )
00190         if self.verbose:
00191             print "dbconf : connecting to %s " % dict(d, passwd="***" )
00192         return d
00193      
00194 
00195     def _check_path(self, path ):
00196         """
00197         Check existance and permissions of path 
00198         """ 
00199         assert os.path.exists( path ), "config path %s does not exist " % path
00200         from stat import S_IMODE, S_IRUSR, S_IWUSR
00201         s = os.stat(path)
00202         assert S_IMODE( s.st_mode ) == S_IRUSR | S_IWUSR , "incorrect permissions, config file must be protected with : chmod go-rw \"%s\" " %  path
00203 
00204 
00205     def configure_cascade(self, sect , path):
00206         """
00207         Interpret the `sect` argument comprised of a either a single section name eg `offline_db`
00208         or a colon delimited list of section names eg `tmp_offline_db:offline_db` 
00209         to provide easy cascade configuration. A single section is of course a special case of a
00210         cascade.  The first(or only) section in zeroth slot is treated specially with its config
00211         parameters being propagated into `self`.
00212 
00213         Caution any settings of `url`, `user`, `pswd`, `host`, `db` are overridden when
00214         the `sect` argument contains a colon. 
00215         """ 
00216         cfp, paths = DBConf.read_cfg( path )
00217         secs = cfp.sections()
00218         csect = sect.split(":")
00219         zsect = csect[0]          ## zeroth slot 
00220 
00221         if self.verbose:
00222             print "configure_cascade sect %s secs %r csect %r " % ( sect, secs, csect )
00223         cascade = {}
00224         for sect in csect:
00225             assert sect in secs  , "section %s is not one of these : %s configured in %s " % ( sect,  secs, paths ) 
00226             cascade[sect] = {}
00227             cascade[sect].update( cfp.items(sect) )
00228  
00229         user = ";".join([ cascade[sect]['user'] for sect in csect ])
00230         pswd = ";".join([ cascade[sect]['password'] for sect in csect ])
00231         host = ";".join([ cascade[sect]['host'] for sect in csect ])
00232         db   = ";".join([ cascade[sect]['database']   for sect in csect ])
00233         url  = ";".join([ DBConf.defaults['url'] % cascade[sect] for sect in csect ])
00234 
00235         self.update( cascade[zsect] )  ##   zeroth slot into self
00236         self.cascade = cascade        ## for debugging only 
00237         return user, pswd, host, db, url
00238  
00239     def dump_env(self, epfx='env_'):
00240         e = {}
00241         for k,v in os.environ.items():
00242             if k.startswith(epfx.upper()):e.update({k:v} )   
00243         return e
00244 
00245 
00246     urls  = property( lambda self:(self.url  % self).split(";") )
00247     users = property( lambda self:(self.user % self).split(";") )
00248     pswds = property( lambda self:(self.pswd % self).split(";") )
00249     fixs  = property( lambda self:(self.fix  % self).split(";") )
00250 
00251     def from_env(cls):
00252         """
00253         Construct :class:`DBConf` objects from environment : 
00254 
00255             :envvar:`ENV_TSQL_URL` 
00256             :envvar:`ENV_TSQL_USER` 
00257             :envvar:`ENV_TSQL_PSWD` 
00258 
00259         """
00260         url  = os.environ.get( 'ENV_TSQL_URL', None )
00261         user = os.environ.get( 'ENV_TSQL_USER', None )
00262         pswd = os.environ.get( 'ENV_TSQL_PSWD', None )
00263         assert url and user and pswd , "DBConf.from_env reconstruction requites the ENV_TSQL_* "
00264         cnf = DBConf(url=url, user=user, pswd=pswd,from_env=True)
00265         return cnf
00266     from_env = classmethod(from_env)
00267 
00268 
00269     def export_(self, **extras):
00270         """
00271         Exports the interpolated configuration into corresponding *DBI* envvars :
00272 
00273             :envvar:`ENV_TSQL_USER`
00274             :envvar:`ENV_TSQL_PSWD`
00275             :envvar:`ENV_TSQL_URL`
00276          
00277         And *DatabaseSvc* envvars for access to non-DBI tables via DatabaseSvc :
00278 
00279             :envvar:`DYB_DB_USER`
00280             :envvar:`DYB_DB_PWSD`
00281             :envvar:`DYB_DB_URL`
00282 
00283         """ 
00284         supplier = extras.pop('supplier', None )
00285         if supplier:
00286             print "export_ supplier is %s " % supplier
00287         else:
00288             supplier = self
00289 
00290         self.export={}
00291         self.export['ENV_TSQL_URL'] =  supplier.url  % self 
00292         self.export['ENV_TSQL_USER'] = supplier.user % self 
00293         self.export['ENV_TSQL_PSWD'] = supplier.pswd % self 
00294 
00295         self.export['DYB_DB_HOST']   = supplier.host % self
00296         self.export['DYB_DB_NAME']   = supplier.db   % self
00297         self.export['DYB_DB_USER']   = supplier.user % self
00298         self.export['DYB_DB_PSWD']   = supplier.pswd % self
00299       
00300         for k,v in extras.items():
00301             self.export[k] = v % self 
00302 
00303 
00304     def read_cfg( cls , path=None ):
00305         """
00306         Classmethod to read config file(s) as specified by `path` argument or :envvar:`DBCONF_PATH` using 
00307         :py:mod:`ConfigParser`  
00308         """
00309         path = path or os.environ.get('DBCONF_PATH', DBConf.defaults['path'] ) 
00310         from ConfigParser import ConfigParser
00311         cfp = ConfigParser(DBConf.prime_parser())
00312         cfp.optionxform = str   ## avoid lowercasing keys, making the keys case sensitive
00313         paths = cfp.read( [os.path.expandvars(os.path.expanduser(p)) for p in path.split(":")] )   
00314         return cfp, paths
00315     read_cfg = classmethod( read_cfg )
00316 
00317 
00318     def has_config( cls , name_=None ):
00319         """
00320         Returns if the named config is available in any of the available DBCONF files 
00321 
00322         For cascade configs (which comprise a colon delimited list of section names) all 
00323         the config sections must be present.
00324 
00325         As this module exposes this in its main, config sections can be tested on command line with::
00326 
00327             ./dbconf.py offline_db && echo y || echo n 
00328             ./dbconf.py offline_dbx && echo y || echo n  
00329             ./dbconf.py tmp_offline_db:offline_db && echo y || echo n
00330             ./dbconf.py tmp_offline_dbx:offline_db && echo y || echo n
00331 
00332         """ 
00333         if not name_:
00334             name_ = os.environ.get('DBCONF',None)
00335         assert name_, "has_config requires an argument if DBCONF envvar is not defined "  
00336         cfp, paths = DBConf.read_cfg()
00337         sects = cfp.sections()
00338         if ":" in name_:
00339             names = name_.split(":")
00340             ok_names = filter(lambda _:_ in sects, names )
00341             return len(names) == len(ok_names)
00342         else:
00343             return name_ in sects           
00344     has_config = classmethod( has_config ) 
00345 
00346     def prime_parser( cls ):
00347         """
00348         Prime parser with "today" to allow expansion of ``%(today)s`` in ``~/.my.cnf``
00349         allowing connection to a daily recovered database named after todays date
00350         """
00351         from datetime import datetime
00352         return dict(today=datetime.now().strftime("%Y%m%d"))
00353     prime_parser = classmethod( prime_parser )
00354 
00355 
00356     def export_to_env(self, **extras):
00357         self.export_(**extras)
00358         print "dbconf:export_to_env from %s section %s " % ( self.path, self.sect ) 
00359         os.environ.update(self.export) 
00360         if self.verbose:
00361             print " ==> %s " % dict(self.export, ENV_TSQL_PSWD='***', DYB_DB_PSWD='***' ) 
00362 
00363     __str__ = lambda self:"\n".join( ["[%s]" % self.sect] + [ "%s = %s" % (k,v ) for k,v in self.items() ] + [""]  )
00364 
00365 
00366 if __name__=='__main__':
00367     import sys
00368     assert len(sys.argv) == 2 , __doc__ % { 'path':sys.argv[0] }
00369     sys.exit( not(DBConf.has_config(sys.argv[1])) )
00370 
| Classes | Job Modules | Data Objects | Services | Algorithms | Tools | Packages | Directories | Tracs |

Generated on Mon Apr 11 20:13:00 2011 for DybPython by doxygen 1.4.7