338 lines
13 KiB
Python
338 lines
13 KiB
Python
|
# -*- mode:Python -*-
|
||
|
#
|
||
|
# This file is part of the Coriolis Software.
|
||
|
# Copyright (c) SU 2012-2020, All Rights Reserved
|
||
|
#
|
||
|
# +-----------------------------------------------------------------+
|
||
|
# | C O R I O L I S |
|
||
|
# | Alliance / Hurricane Interface |
|
||
|
# | |
|
||
|
# | 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.Configuration`` : to be used in ``with`` construct.
|
||
|
* ``overlay.CfgCache`` : A cache for Cfg parameters.
|
||
|
"""
|
||
|
|
||
|
from __future__ import print_function
|
||
|
import Cfg
|
||
|
import Hurricane
|
||
|
|
||
|
|
||
|
class UpdateSession ( object ):
|
||
|
"""
|
||
|
Context manager for a GO update session. See Hurricane reference manual
|
||
|
for an info on Hurricane::UpdateSession class.
|
||
|
"""
|
||
|
|
||
|
def __enter__ ( self ):
|
||
|
Hurricane.UpdateSession.open()
|
||
|
|
||
|
def __exit__( self, *args ):
|
||
|
Hurricane.UpdateSession.close()
|
||
|
|
||
|
|
||
|
class Configuration:
|
||
|
"""
|
||
|
Allow access to Cfg parameter as attributes. For attribute syntax,
|
||
|
the dot (.) used in C++ or raw access is replaced by an underscore (_)
|
||
|
in Python mode.
|
||
|
|
||
|
Also provides a context manager.
|
||
|
"""
|
||
|
|
||
|
PRIORITY_USE_DEFAULT = Cfg.Parameter.Priority.UseDefault
|
||
|
PRIORITY_APPLICATION_BUILTIN = Cfg.Parameter.Priority.ApplicationBuiltin
|
||
|
PRIORITY_CONFIGURATION_FILE = Cfg.Parameter.Priority.ConfigurationFile
|
||
|
PRIORITY_USER_FILE = Cfg.Parameter.Priority.UserFile
|
||
|
PRIORITY_COMMAND_LINE = Cfg.Parameter.Priority.CommandLine
|
||
|
PRIORITY_INTERACTIVE = Cfg.Parameter.Priority.Interactive
|
||
|
|
||
|
def __init__ ( self, priority=None ):
|
||
|
self._priority = priority
|
||
|
|
||
|
def __enter__( self ):
|
||
|
if self._priority is not None:
|
||
|
Cfg.Configuration.pushDefaultPriority( self._priority )
|
||
|
return self
|
||
|
|
||
|
def __setattr__( self, attr, val ):
|
||
|
if attr.startswith("_"):
|
||
|
self.__dict__[attr] = val
|
||
|
return
|
||
|
attr = attr.replace("_", ".")
|
||
|
if isinstance(val, bool):
|
||
|
Cfg.getParamBool(attr).setBool( val )
|
||
|
elif isinstance(val, int):
|
||
|
p = Cfg.getParamInt( attr ) # all params have a type
|
||
|
if p.type == 'Enumerate':
|
||
|
Cfg.getParamEnumerate(attr).setInt( val )
|
||
|
else:
|
||
|
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:
|
||
|
Cfg.getParamPercentage(attr).setPercentage( float(val[:-1]) )
|
||
|
else:
|
||
|
Cfg.getParamString(attr).setString( val )
|
||
|
|
||
|
def __exit__( self, *args ):
|
||
|
if self._priority is not None:
|
||
|
Cfg.Configuration.popDefaultPriority()
|
||
|
|
||
|
|
||
|
class CachedParameter ( object ):
|
||
|
|
||
|
def __init__ ( self, path, v ):
|
||
|
self.path = path
|
||
|
self._v = None
|
||
|
self.v = v
|
||
|
self.vRange = [ None, None ]
|
||
|
self.vEnum = []
|
||
|
self.create = True
|
||
|
self.cacheRead()
|
||
|
|
||
|
@property
|
||
|
def v ( self ): return self._v
|
||
|
|
||
|
@v.setter
|
||
|
def v ( self, value ):
|
||
|
if value is not None: self._v = value
|
||
|
|
||
|
def __str__ ( self ):
|
||
|
if isinstance(self.v,str): s = '"{}"'.format(self.v)
|
||
|
else: s = '{}'.format(self.v)
|
||
|
if self.vRange[0] is not None or self.vRange[1] is not None:
|
||
|
s += ' [{}:{}]'.format(self.vRange[0],self.vRange[1])
|
||
|
if self.vEnum:
|
||
|
s += ' ('
|
||
|
for i in range(len(self.vEnum)):
|
||
|
if i: s += ', '
|
||
|
s += '{}:"{}"'.format(self.vEnum[i][1],self.vEnum[i][0])
|
||
|
s += ')'
|
||
|
return s
|
||
|
|
||
|
def cacheWrite ( self ):
|
||
|
""""
|
||
|
Commit the value of parameter ``self.path`` to ``self.v`` in Cfg.
|
||
|
Percentage are set as Double and Enumerate as Int.
|
||
|
"""
|
||
|
if Cfg.hasParameter(self.path):
|
||
|
confDb = Cfg.Configuration.get()
|
||
|
p = confDb.getParameter( self.path )
|
||
|
else:
|
||
|
if len(self.vEnum): p = Cfg.getParamEnumerate( self.path )
|
||
|
elif isinstance(self.v,bool ): p = Cfg.getParamBool ( self.path )
|
||
|
elif isinstance(self.v,int ): p = Cfg.getParamInt ( self.path )
|
||
|
elif isinstance(self.v,float): p = Cfg.getParamDouble ( self.path )
|
||
|
else: p = Cfg.getParamString ( self.path )
|
||
|
if p.type == Cfg.Parameter.Type.Enumerate: p.setInt ( self.v )
|
||
|
elif p.type == Cfg.Parameter.Type.Int: p.setInt ( self.v )
|
||
|
elif p.type == Cfg.Parameter.Type.Bool: p.setBool ( self.v )
|
||
|
elif p.type == Cfg.Parameter.Type.Double: p.setDouble ( self.v )
|
||
|
elif p.type == Cfg.Parameter.Type.Percentage: p.setDouble ( self.v*100.0 )
|
||
|
else: p.setString ( str(self.v) )
|
||
|
if self.create:
|
||
|
if len(self.vEnum):
|
||
|
for item in self.vEnum:
|
||
|
p.addValue( item[0], item[1] )
|
||
|
if self.vRange[0] is not None: p.setMin( self.vRange[0] )
|
||
|
if self.vRange[1] is not None: p.setMax( self.vRange[1] )
|
||
|
|
||
|
def cacheRead ( self ):
|
||
|
""""Get the value of parameter ``self.path`` from Cfg."""
|
||
|
if not Cfg.hasParameter(self.path):
|
||
|
self.create = True
|
||
|
return
|
||
|
if self.v is not None: return
|
||
|
confDb = Cfg.Configuration.get()
|
||
|
p = confDb.getParameter( self.path )
|
||
|
if p:
|
||
|
if p.type == Cfg.Parameter.Type.Enumerate: self.v = p.asInt()
|
||
|
elif p.type == Cfg.Parameter.Type.Int:
|
||
|
self.v = p.asInt()
|
||
|
elif p.type == Cfg.Parameter.Type.Bool: self.v = p.asBool()
|
||
|
elif p.type == Cfg.Parameter.Type.String: self.v = p.asString()
|
||
|
elif p.type == Cfg.Parameter.Type.Double: self.v = p.asDouble()
|
||
|
elif p.type == Cfg.Parameter.Type.Percentage: self.v = p.asDouble()/100.0
|
||
|
else: self.v = p.asString()
|
||
|
|
||
|
|
||
|
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()
|
||
|
|
||
|
If a cache parameter is assigned to ``None``, it triggers the
|
||
|
loading of the value from the disk, it it exists.
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
# Setup of a CfgCache parameter.
|
||
|
cache = CfgCache('')
|
||
|
cache.katana.eventsLimit = None
|
||
|
# The parameter will read it's value from the disk (4000000).
|
||
|
|
||
|
|
||
|
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.
|
||
|
"""
|
||
|
|
||
|
def __enter__( self ):
|
||
|
return self
|
||
|
|
||
|
def __exit__( self, *args ):
|
||
|
self.apply()
|
||
|
self.display()
|
||
|
|
||
|
def __init__ ( self, path='', priority=None ):
|
||
|
"""Create a new CfgCache with a ``path`` as parent path."""
|
||
|
self._priority = priority
|
||
|
self._path = path
|
||
|
self._rattr = {}
|
||
|
|
||
|
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
|
||
|
vRange = None
|
||
|
vEnum = None
|
||
|
if isinstance(v,list ): vRange = v; v = None
|
||
|
if isinstance(v,tuple): vEnum = v; v = None
|
||
|
if not attr in self._rattr:
|
||
|
self._rattr[ attr ] = CachedParameter( self._path+'.'+attr, v )
|
||
|
if vRange is not None: self._rattr[ attr ].vRange = vRange
|
||
|
elif vEnum is not None: self._rattr[ attr ].vEnum = vEnum
|
||
|
else: self._rattr[ attr ].v = v
|
||
|
|
||
|
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 attr in self._rattr:
|
||
|
path = self._path+'.'+attr if len(self._path) else attr
|
||
|
self._rattr[attr] = CfgCache( path, self._priority )
|
||
|
if isinstance(self._rattr[attr],CachedParameter):
|
||
|
return self._rattr[attr].v
|
||
|
return self._rattr[attr]
|
||
|
|
||
|
def _hasCachedParam ( self, elements ):
|
||
|
if not elements[0] in self._rattr:
|
||
|
return False
|
||
|
if len(elements) == 1:
|
||
|
return True
|
||
|
rattr = self._rattr[ elements[0] ]
|
||
|
if not isinstance(rattr,CfgCache):
|
||
|
return False
|
||
|
return rattr._hasCachedParam( elements[1:] )
|
||
|
|
||
|
def hasCachedParam ( self, attr ):
|
||
|
return self._hasCachedParam( attr.split('.') )
|
||
|
|
||
|
def apply ( self, priority=None ):
|
||
|
"""Apply the parameters values stored in the cache to the ``Cfg`` database."""
|
||
|
if priority is None: priority = self._priority
|
||
|
if not len(self._path) and priority is not None:
|
||
|
Cfg.Configuration.pushDefaultPriority( priority )
|
||
|
for attrName in self._rattr.keys():
|
||
|
if isinstance(self._rattr[attrName],CfgCache):
|
||
|
self._rattr[attrName].apply()
|
||
|
continue
|
||
|
self._rattr[attrName].cacheWrite()
|
||
|
if not len(self._path) and priority is not None:
|
||
|
Cfg.Configuration.popDefaultPriority()
|
||
|
#self.display()
|
||
|
|
||
|
def display ( self ):
|
||
|
"""Print all the parameters stored in that CfgCache."""
|
||
|
if not len(self._path):
|
||
|
print( ' o Applying configuration (CfgCache):' )
|
||
|
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]) )
|
||
|
|