diff --git a/crlcore/python/helpers/overlay.py b/crlcore/python/helpers/overlay.py index 3ba25cf6..9c8fe8e0 100644 --- a/crlcore/python/helpers/overlay.py +++ b/crlcore/python/helpers/overlay.py @@ -1,13 +1,31 @@ +# -*- mode:Python; explicit-buffer-name: "__init__.py" -*- +# +# This file is part of the Coriolis Software. +# Copyright (c) UPMC 2012-2018, All Rights Reserved +# +# +-----------------------------------------------------------------+ +# | C O R I O L I S | +# | C o r i o l i s / C h a m s B u i l d e r | +# | | +# | Author : Jean-Paul Chaput | +# | E-mail : Jean-Paul.Chaput@lip6.fr | +# | =============================================================== | +# | Python : "./crlcore/helpers/overlay.py" | +# +-----------------------------------------------------------------+ +# +# Those classes are based on the work of Jock Tanner from Libre-SOC. + """ Overlay to make some C++ objects provide a more Pythonic interface. Contains: * ``overlay.UpdateSession`` : to be used in ``with`` construct. -* ``overlay.Cfg`` : to be used in ``with`` construct. +* ``overlay.Configuration`` : to be used in ``with`` construct. +* ``overlay.CfgCache`` : A cache for Cfg parameters. """ - +from __future__ import print_function import Cfg import Hurricane @@ -64,6 +82,7 @@ class Configuration: Cfg.getParamInt(attr).setInt( val ) elif isinstance(val, long): p = Cfg.getParamInt( attr ) # all params have a type + p.setInt( val ) elif isinstance(val, float): p = Cfg.getParamDouble( attr ).setDouble( val ) elif '%' in val: @@ -75,3 +94,167 @@ class Configuration: if self._priority is not None: Cfg.Configuration.popDefaultPriority() + +class CfgCache ( object ): + """ + CgfCache cache a set of configuration parameters. The values of the + parameters are not set in the system *until* the ``apply()`` function + is called. + + If a parameter do not exists in the ``Cfg`` module, it is created + when ``apply()`` is called. Be aware that it is not able to guess + the right type between Double and Percentage or Int and Enumerate. + It will, by default, only create Double or Int. So, when setting + Percentage or Enumerate, be sure that they exists beforehand in + the ``Cfg`` module. + + The attributes of CfgCache exactly mimic the behavior of the + ``Cfg`` parameter string identifiers. For example: + + .. code-block:: python + + # Direct access to a Cfg parameter. + p = Cfg.getParamInt('katana.eventsLimit').setInt( 4000000 ) + + # Setup of a CfgCache parameter. + cache = CfgCache('') + cache.katana.eventsLimit = 4000000 + + # ... + # Effective setting of the Cfg parameter. + cache.apply() + + + This is done by overloading ``__setattr__()`` and ``__getattr__()`` + which recursively create CfgCache objects for intermediate levels + attributes (in the previous example, a CfgCache for ``katana`` + will automatically be created). To separate between attributes + that are part of configuration parameters and attributes belonging + to CfgCache itself, we prepend a '_' to the laters. + + .. note:: It is important to understand the difference of behavior + with ``Configuration``, the former set the parameters + at once, it directly act on the ``Cfg`` settings. + The later keep a state and set the ``Cfg`` parameters + *only* when ``apply()`` is called. + """ + + @staticmethod + def setCfgParameter ( paramPath, value ): + """" + Set the value of parameter ``paramPath`` to ``value`` in Cfg. + Percentage are set as Double and Enumerate as Int. + """ + if Cfg.hasParameter(paramPath): + confDb = Cfg.Configuration.get() + p = confDb.getParameter( paramPath ) + else: + if isinstance(value,bool ): p = Cfg.getParamBool ( paramPath ) + elif isinstance(value,int ): p = Cfg.getParamInt ( paramPath ) + elif isinstance(value,long ): p = Cfg.getParamInt ( paramPath ) + elif isinstance(value,float): p = Cfg.getParamDouble( paramPath ) + else: p = Cfg.getParamString( paramPath ) + + if p.type == Cfg.Parameter.Type.Enumerate: p.setInt ( value ) + elif p.type == Cfg.Parameter.Type.Int: p.setInt ( value ) + elif p.type == Cfg.Parameter.Type.Bool: p.setBool ( value ) + elif p.type == Cfg.Parameter.Type.Double: p.setDouble( value ) + elif p.type == Cfg.Parameter.Type.Percentage: p.setDouble( value*100.0 ) + else: p.setString( str(value) ) + + @staticmethod + def getDefaultCfgParameter ( paramPath ): + """"Get the value of parameter ``paramPath`` from Cfg.""" + if not Cfg.hasParameter(paramPath): + raise AttributeError( 'CfgCache.getDefaultCfgParameter(): Undefined "{}"'.format(paramPath) ) + confDb = Cfg.Configuration.get() + p = confDb.getParameter( paramPath ) + if p: + if p.type == Cfg.Parameter.Type.Enumerate: return p.asInt() + if p.type == Cfg.Parameter.Type.Int: return p.asInt() + if p.type == Cfg.Parameter.Type.Bool: return p.asBool() + if p.type == Cfg.Parameter.Type.String: return p.asString() + if p.type == Cfg.Parameter.Type.Double: return p.asDouble() + if p.type == Cfg.Parameter.Type.Percentage: return p.asDouble()/100.0 + return p.asString() + + def __init__ ( self, path ): + """Create a new CfgCache with a ``path`` as parent path.""" + self._path = path + self._rattr = {} + return + + def __setattr__ ( self, attr, v ): + """ + Recursively set an attribute. Attributes names starting by an '_' are + treated as belonging to *this* object (self). + + How does the recursive attributes/CfgCache works? Assumes that we + are doing: + + .. code-block:: python + + # Setup of a CfgCache parameter. + cache = CfgCache('') + cache.katana.eventsLimit = 4000000 + + The explicit call sequence will be: + + .. code-block:: python + + cache.__getattr__('katana').__setattr__( 'eventsLimit', 4000000 ) + + 1. For the intermediate hierarchy level ``katana``, it is __getattr__() + which is called, if the attribute do not exists, we create a new + CfgCache(). + + 2. Second, and only then, __setattr__() is called, which will create a + parameter entry named ``eventsLimit``. + + The decision of whether create a parameter entry *or* a CfgCache + intermediate level will always be correctly handled because prior + to any access, an attribute needs to be set. So we always have + first a call chain of __getattr__() with one final __setattr__(). + For any subsequent access to ``cache.katana.eventsLimit``, as + the attribute already exists, there is no type creation problem. + """ + if attr[0] == '_': + object.__setattr__( self, attr, v ) + return + if v is None: + v = CfgCache.getDefaultCfgParameter( self._path+'.'+attr ) + self._rattr[ attr ] = v + return + + def __getattr__ ( self, attr ): + """ + Get an attribute, if it doesn't exists, then we are in an intermediate + level like ``katana``, so create a new sub CfgCache for that attribute. + """ + if not self._rattr.has_key(attr): + path = self._path+'.'+attr if len(self._path) else attr + self._rattr[attr] = CfgCache( path ) + return self._rattr[attr] + + def apply ( self, priority=Cfg.Parameter.Priority.UserFile ): + """Apply the parameters values stored in the cache to the ``Cfg`` database.""" + if not len(self._path): + Cfg.Configuration.pushDefaultPriority( priority ) + for attrName in self._rattr.keys(): + if isinstance(self._rattr[attrName],CfgCache): + self._rattr[attrName].apply() + continue + CfgCache.setCfgParameter( self._path+'.'+attrName,self._rattr[attrName] ) + if not len(self._path): + Cfg.Configuration.popDefaultPriority() + + def display ( self ): + """Print all the parameters stored in that CfgCache.""" + if not len(self._path): + print( 'Configuration (Cfg) cache:' ) + for attrName in self._rattr.keys(): + if isinstance(self._rattr[attrName],CfgCache): + self._rattr[attrName].display() + continue + print( '* {}.{} = {}'.format(self._path,attrName,self._rattr[attrName]) ) +