Exemplo n.º 1
0
    def __init__(self, **kwargs):
        '''

        :Keyword arguments:
            - **config*: load a configuration (:meth:`load_config()`)
            - **logger**: A :class:`Logger` instance.
            - **logger_<param>**: ``<param>`` is passed to the
              :class:`Logger` constructor. Not used if **logger** is passed.

        '''
        # Setup logging
        lkw = kwfilter(kwargs, 'logger', {'name':self.__class__.__name__})
        if isinstance(lkw.get('config', None), Object):
            lkw['config'] = lkw['config'].get_logger()
        lkw['name_filters'] = list(lkw.get('name_filters', [])) + [self.__class__.__name__]
        if 'logger' in lkw:
            self._logger = lkw['logger']
            if not isinstance(self._logger, Logger):
                vacumm_warning(self.__class__.__name__.split('.')[-1]+
                    '{} is initialised with an invalid logger type')
        else:
            self._logger = Logger(**lkw)
        self._logger.skipCaller(self.get_class_logger().skipCaller())
        # Load passed or default configuration
        self.load_config(kwargs.get('config', None),  cfgpatch=kwargs.get('cfgpatch', None))
Exemplo n.º 2
0
    def main(obj, cls, args=()):
        parser = cls.get_argparser()
        args = parser.parse_args(args)
        Logger.apply_class_argparser_options(args)

        if obj is None:
            app = cls()
        else:
            app = obj

        return 1 if app.run(args) else 0
Exemplo n.º 3
0
 def populate_argparser(cls, parser):
     parser.add_argument('-s',
                         '--spcfile',
                         help='Specification file to use at startup')
     parser.add_argument('-c',
                         '--cfgfile',
                         help='Configuration file to use at startup')
     parser.add_argument('-n',
                         '--session',
                         help='Name of the session to use at startup')
     parser.add_argument('--configuration_directory',
                         help='Aplication configuration directory')
     Logger.add_argparser_options(parser=parser)
Exemplo n.º 4
0
 def _init_class(cls, name, bases, dct):
     '''
     Class initialization method, called when the class is defined.
     '''
     # Setup class logging
     cls._class_logger = Logger(name=name, name_filters=[name])
     add_logging_proxies(cls)
     # Configuration management
     if not hasattr(cls, '__config_managers'):
         cls.__config_managers = {}
     if not hasattr(cls, '__config_defaults'):
         cls.__config_defaults = {}
     cls.load_default_config(nested=True)
     cls.init_class(name, bases, dct)
Exemplo n.º 5
0
class Object(object):
    '''
    Vacumm's base class proving common usefull features:

        - configuration management
            - keeping trace of parent class configs
            - allowing per-instance config usage (default to class config)
        - logging
        - debugging
            - tracking (pdb)
            - exceptions details
            - process memory usage

    Most features work at class level to reduce variables in instances.

    This class is designed to be implemented by the main working classes,
    but there is no real restriction about that.

    The default configuration is automatically loaded when importing the class module, this
    behavior is also applied for subclasses in other modules.

    Instance initial configuration (:func:`get_config`) is a copy of the class
    configuration (:func:`get_default_config`) made at instance creation time.

    '''
    # All subclasses will use this metaclass
    __metaclass__ = Class
    # For example, the following classes definition:
    #   In a vacumm.a.py module:
    #     from vacumm import Object
    #     class A(Object): pass
    #   In b vacumm.b.py module:
    #     import a
    #     class B(a.A): pass
    #
    # will produce the following methods calls at class definition (import):
    #
    #   Class.__new__ (in Class): cls: <class 'vacumm.Class'>
    #   Object.__init__ (in Class): self: <class 'vacumm.Object'>
    #   Class.__new__ (in Class): cls: <class 'vacumm.Class'>
    #   A.__init__ (in Class): self: <class 'a.A'>
    #   Class.__new__ (in Class): cls: <class 'vacumm.Class'>
    #   B.__init__ (in Class): self: <class 'b.B'>
    #
    # and then at instanciation (b=B()):
    #
    #   B.__new__ (in Object): cls: <class 'b.B'>
    #   B.__init__ (in Object): cls: <b.B object at 0x2ad3b50>
    #   A.__init__
    #   B.__init__
    #

    _log_level = 'info'
    _cfg_debug = False
    _log_obj_stats = False

    # Not yet needed
    #def __new__(cls, *args, **kwargs):
    #    #print '%s.%s (in Object): cls: %s, args: %s, kwargs: %s'%(cls.__name__, func_name(), cls, args, kwargs)
    #    #print '%s.%s (in Object): cls: %s'%(cls.__name__, func_name(), cls)
    #    #
    #    return object.__new__(cls, *args, **kwargs)

    # ==========================================================================
    # Class initializer (not instance initializer !!!)
    # ==========================================================================
    @classmethod
    def _init_class(cls, name, bases, dct):
        '''
        Class initialization method, called when the class is defined.
        '''
        # Setup class logging
        cls._class_logger = Logger(name=name, name_filters=[name])
        add_logging_proxies(cls)
        # Configuration management
        if not hasattr(cls, '__config_managers'):
            cls.__config_managers = {}
        if not hasattr(cls, '__config_defaults'):
            cls.__config_defaults = {}
        cls.load_default_config(nested=True)
        cls.init_class(name, bases, dct)

    @classmethod
    def init_class(cls, name, bases, dct):
        '''Redefine this method if you need class initialization'''
        pass

    # ==========================================================================
    # Logging features
    # ==========================================================================

    @classmethod
    def get_class_logger(cls):
        'Return the :class:`Logger` instance bound to this class.'
        return cls._class_logger

    @classmethod
    def set_class_logger(cls, logger):
        'Set the :class:`Logger` instance bound to this class.'
        cls._class_logger = logger

    @classinstancemethod
    def get_logger(obj, cls):
        '''
        Return the :class:`Logger` instance bound to this class or instance,
        according to the way this method is called (from class or instance).
        '''
        return cls._class_logger if obj is None else obj._logger

    @classinstancemethod
    def set_logger(obj, cls, logger):
        '''
        Set the :class:`Logger` instance bound to this class or instance,
        according to the way this method is called (from class or instance).
        '''
        if obj is None: cls._class_logger = logger
        else: obj._logger = logger

    logger = property(lambda o,*a,**k:o.get_logger(*a,**k), lambda o,*a,**k:o.set_logger(*a,**k), None,
        'A :class:`Logger` instance. You can use this object for all logging '
        'operations related to this class')

    # ==========================================================================
    # Configuration features
    # ==========================================================================

    @classmethod
    def get_config_spec_file(cls):
        '''Return (and define) the class specification file path'''
        try:
            cfgfile = '%s.ini'%(os.path.splitext(os.path.realpath(inspect.getfile(cls)))[0])
        except TypeError: # occure if creating a subclass in interactive python shell
            cfgfile = None
        return cfgfile

    @classmethod
    def get_parent_config_spec(cls):
        '''Get the merged config specifications of all parents'''
        cfg = None
        for c in cls.__bases__:
            if not hasattr(c, 'get_config_spec'): continue
            cs = c.get_config_spec()
            if cfg is None:
                cfg = cs
            else:
                cfg = dict_merge(cfg, cs)
        return cfg


    @classmethod
    def get_config_spec(cls):
        '''Load the config specs as ConfigObj object

        It merges the specs of the current class and those of parents classes
        '''
        cfg = None
        spec = cls.get_config_spec_file()

        # If specification (and so defaults) file defined and exists
        if spec and os.path.isfile(spec):

            # Load (temporary) the file
            cfg = configobj.ConfigObj(spec, list_values=False, interpolation=False)

            # NOTE: list_values=False, interpolation=False are set because list values
            #       are handled by the manager (parse error otherwise)

            # If a config section lookup is defined and present, load the section
            sec = cls.get_config_section_name()
            if sec and sec in cfg:
                #cfg = configobj.ConfigObj(cfgspec[sec], list_values=False, interpolation=False)
                cfg = configobj.ConfigObj(cfg[sec])

        # Merge with parents
        pcfg = cls.get_parent_config_spec()
        if cfg is None:
            cfg = pcfg
        elif pcfg is not None:
            cfg = dict_merge(cfg, pcfg, mergesubdicts=False)
        return cfg


    @classmethod
    def get_config_section_name(cls):
        '''Return (and define) the class specification section name'''
        return cls.__name__

    @classmethod
    def get_config_manager(cls, reload=False, encoding=None):
        '''
        Get the configuration manager for this class (cls).
        This manager and its underlying configuration specification
        must not be dynamically changed as it is fixed at design time.

        .. note:: this method is also the config manager lazy loader

        '''
        # Populate this class config manager if not yet done or reload request
        if not cls in cls.__config_managers or reload:

            # Get the ConfigObj object of specifications
            cfg = cls.get_config_spec()

            # NOTE: If no spec / no class section, class use empty spec
            cfgmgr = ConfigManager(cfg, encoding=encoding)
            cls.__config_managers[cls] = cfgmgr
            if cls._cfg_debug:
                cls.debug('Loaded config manager of class %s with spec:\n  %s', cls.__name__, '\n  '.join(cfgmgr._configspec.write()))

        return cls.__config_managers[cls]

    @classmethod
    def load_default_config(cls, config=None, nested=None, apply=True, encoding=None):
        '''Load / update the class (unique) default configuration'''
        cfgmgr = cls.get_config_manager(encoding=encoding)
        cfgdef = cfgmgr.defaults()
        cfgsec = cls.get_config_section_name()
        if isinstance(nested, basestring):
            cfgsec = nested
        # Change the class default config if required
        if config is not None:
            cfg = configobj.ConfigObj(config, encoding=encoding)
            # If a config section lookup is required
            # Otherwise, the whole passed config will be taken
            if nested:
                if cfgsec in cfg:
                    cfg = configobj.ConfigObj(cfg[cfgsec], encoding=encoding)
                # Section not found, use empty config
                else:
                    cfg = configobj.ConfigObj(encoding=encoding)
            cfgdef = cfgmgr.load(cfg)
        cls.__config_defaults[cls] = cfgdef
        if apply:
            cls.apply_default_config()
        if cls._cfg_debug:
            cls.debug(
                'Loaded %s default configuration:'
                '\n  section:  %s'
                '\n  nested:   %s'
                '\n  from:     %s'
                '\n  loaded:   '
                '\n  %s',
                cls, cfgsec, nested, config, '\n  '.join(cls.get_default_config().write()))

    @classmethod
    def get_default_config(cls, encoding=None):
        '''Get the default configuration (copy)'''
        return configobj.ConfigObj(cls.__config_defaults[cls], encoding=encoding)

    @classmethod
    def get_default_config_str(cls, encoding=None):
        '''Get the default configuration as a string'''
        return '\n  '.join(configobj.ConfigObj(cls.__config_defaults[cls], encoding=encoding).write())

    @classmethod
    def apply_default_config(cls, config=None, encoding=None):
        '''
        This will turn on debug mode of various features (config, objects stats)
        if the loglevel of nested Logger configuration is debug.

        Set the default log level for the newly created objects based on nested
        Logger configuration.

        Subclasses may override this to apply/update according to the new config

        :Params:
            - **config**: The new config loaded by :meth:`load_default_config()`.

        .. note::
            - overriding this method will obviously shunt its default beahvior, you'll then have to call original method if needed

        '''
        if config is None: config = cls.get_default_config(encoding=encoding)
        #cls.get_logger().load_config(config, nested=True)
        loglvl = logger.get_level_name() #loglvl = cls.get_logger().get_level_name().lower()
        isdbg = logger.is_debug() #isdbg = loglvl == 'debug'
        cls._log_level = loglvl
        cls._cfg_debug = config.get('cfg_debug', isdbg or cls._cfg_debug)
        cls._log_obj_stats = config.get('log_obj_stats', isdbg or cls._log_obj_stats)

    @classmethod
    def from_config(cls, config, *args, **kwargs):
        '''Create a cls instance using args and kwargs and load config.

        The **nested** named argument (in kwargs) is extracted before
        creating the instance and then passed to load_config.

        :Params:
            - **config**: A configuration file (str) or object (ConfigObj).
            - **args** and **kwargs**: Passed to the object constructor, without parmeters described above.

        :Return:
            - The created object of class cls

        '''
        loadkw = dict(((a,kwargs.pop(a)) for a in ('nested', 'apply') if a in kwargs))
        obj = cls(*args, **kwargs)
        obj.load_config(config, **loadkw)
        return obj

    def load_config(self, config=None, nested=None, apply=True, cfgpatch=None, encoding=None, **kwargs):
        '''Load / update the instance configuration

        :Params:
            - **config**: A configuration file (str) or object (ConfigObj) or
              None to load defaults, or an :class:`~argparse.ArgumentParser` object.
            - **nested**: Load from a nested config section instead of the whole config.
                          If True, use the section name returned by :meth:`get_config_section_name()`
                          Else if a string, use the section name defined by the **nested** string
            - **cfgpatch**: A manual patch to apply to the config once loaded.
            - Other options are passed to
              :meth:`~vacumm.misc.config.ConfigManager.arg_parse`.

        :Return: A :class:`ConfigObj` object or :class:`ConfigObj`,options tuple if
            an :class:`~argparse.ArgumentParser` object has been passed
        '''
        mgr = self.get_config_manager(encoding=encoding)
        sec = self.get_config_section_name()
        if not hasattr(self, '_config'):
            self._config = self.get_default_config(encoding=encoding)
        self._options = None
        if config is not None:
            if isinstance(nested, basestring):
                    sec = nested
            if isinstance(config, ArgumentParser):
                self._config, self._options = mgr.arg_parse(config,
                    nested=nested and sec or nested, getargs=True, **kwargs)
            else:
                cfg = configobj.ConfigObj(config, interpolation=False, encoding=encoding)
                # If a nested section lookup is required
                # Otherwise, the whole passed config will be taken
                if nested and sec and sec in cfg: # If not found, self._config remain unchanged
                    cfg = configobj.ConfigObj(cfg[sec], interpolation=False, encoding=encoding)
                self._config = mgr.load(cfg)
        if cfgpatch is not None:
            if not isinstance(cfgpatch, list):
                cfgpatch = [cfgpatch]
            for patch in cfgpatch:
                mgr.cfg_patch(self._config, patch)
        if apply:
            self.apply_config(self._config)
        if self._cfg_debug:
            self.debug(
                'Loaded %s configuration:'
                '\n  section:  %s'
                '\n  nested:   %s'
                '\n  from:     %s'
                '\n  loaded:   '
                '\n  %s',
                self.__class__, sec, nested, config, '\n  '.join(self._config.write()))
        return self._config

    def save_config(self, outfile=None, nested=None):
        if isinstance(outfile, basestring):
            outfile, close = file(outfile, 'w'), True
        else:
            close = False
        config = configobj.ConfigObj(self._config)
        if nested:
            if isinstance(nested, basestring):
                sec = nested 
            else:
                sec = self.get_config_section_name()
            # XXX config.dict() is required, otherwise section will not be correctly written ([] missing)
            config = configobj.ConfigObj({sec:config.dict()})
        r = config.write(outfile)
        if close:
            outfile.close()
        return r

    def get_options(self):
        """Get :attr:`options`"""
        return getattr(self, '_options', None)
    options = property(fget=get_options, doc='Options loaded from the commandline parser or None')

    def get_config(self, copy=True):
        '''Get the instance's config'''
        if copy:
            return configobj.ConfigObj(self._config)
        return self._config
    config = property(fget=get_config, doc='Current configuration')

    def get_config_str(self):
        '''Get the instance's config as a string'''
        return '\n  '.join(configobj.ConfigObj(self._config).write())

    def apply_config(self, config):
        '''Subclasses may override this to apply/update according to the new config

        :Params:
            - **config**: The new config loaded by :meth:`load_config()`.

        .. note::
            - overriding this method will obviously shunt its default beahvior,
              you'll then have to call original method if needed

        '''
        self.logger.load_config(config, nested=True)
        # ...


    # ==========================================================================
    # Debugging/Introspection features
    # ==========================================================================

    @classmethod
    def func_name(cls, iframe=0):
        __doc__ = func_name.__doc__
        return func_name(iframe+1)

    @staticmethod
    def stack_trace(iframe=0):
        __doc__ = stack_trace.__doc__
        return stack_trace(iframe+1)

    @classmethod
    def exception_trace(cls):
        '''Return a huge detailed exception traceback'''
        return getDetailedExceptionInfo()

    @classmethod
    def trace(cls, iframe=0, iftty=True):
        '''
        Start pdb debugger

        :Params:
            - **iframe**: frame index entry point of the debugger, relative to the caller
            - **iftty**: if True, disable this call in a non interactive execution

        .. note::
            - For debugging purpose only: do not let trace calls in a production
              environment, even if an interactive test is done !
        '''
        if sys.stdin.isatty():# and sys.stdout.isatty():
            pdb.Pdb().set_trace(sys._getframe(1+iframe))

    @classmethod
    def describe(cls, obj, **kwargs):
        kw = dict(stats=cls._log_obj_stats, format=cls.pformat)
        kw.update(kwargs)
        return describe(obj, **kw)

    @classmethod
    def pformat(cls, obj, indent=2, width=80, depth=None):
        '''Pretty print an object'''
        # default is: indent=1, width=80, depth=None
        return pprint.pformat(obj, indent, width, depth)

    # ==========================================================================
    # Other stuff
    # ==========================================================================

    @classmethod
    def kwfilter(cls, kwargs, filters=None, *args, **kwa):
        '''
        Shortcut to :func:`vacumm.misc.misc.kwfilter` with a filters argument which
        defaults to this class lowercase name.
        '''
        if filters is None: filters = cls.__name__.lower()
        return kwfilter(kwargs, filters, *args, **kwa)

    # ==========================================================================
    # Initializer
    # ==========================================================================
    def __init__(self, **kwargs):
        '''

        :Keyword arguments:
            - **config*: load a configuration (:meth:`load_config()`)
            - **logger**: A :class:`Logger` instance.
            - **logger_<param>**: ``<param>`` is passed to the
              :class:`Logger` constructor. Not used if **logger** is passed.

        '''
        # Setup logging
        lkw = kwfilter(kwargs, 'logger', {'name':self.__class__.__name__})
        if isinstance(lkw.get('config', None), Object):
            lkw['config'] = lkw['config'].get_logger()
        lkw['name_filters'] = list(lkw.get('name_filters', [])) + [self.__class__.__name__]
        if 'logger' in lkw:
            self._logger = lkw['logger']
            if not isinstance(self._logger, Logger):
                vacumm_warning(self.__class__.__name__.split('.')[-1]+
                    '{} is initialised with an invalid logger type')
        else:
            self._logger = Logger(**lkw)
        self._logger.skipCaller(self.get_class_logger().skipCaller())
        # Load passed or default configuration
        self.load_config(kwargs.get('config', None),  cfgpatch=kwargs.get('cfgpatch', None))
Exemplo n.º 6
0
#!/usr/bin/env python
# -*- coding: utf8 -*-
"""Passer par un logger"""

from vacumm.misc.log import Logger  # logger avancé

# from vacumm.misc.io import Logger # logger plus simple
from vcmq import dict_filter


# Initialisation
logger = Logger("TEST", level="info", format="[%(asctime)s %(name)s %(levelname)s] %(message)s")


# Test
logger.info("mon info")
logger.warning("mon warning")
logger.debug("mon debug")  # -> RETESTER AVEC UN SET_LEVEL POUR DEBUG
logger.error("mon erreur")
Exemplo n.º 7
0
#!/usr/bin/env python
# -*- coding: utf8 -*-
"""Passer par un logger"""

from vacumm.misc.log import Logger  # logger avancé
#from vacumm.misc.io import Logger # logger plus simple
from vcmq import dict_filter

# Initialisation
logger = Logger('TEST',
                level='info',
                format='[%(asctime)s %(name)s %(levelname)s] %(message)s')

# Test
logger.info('mon info')
logger.warning('mon warning')
logger.debug('mon debug')  # -> RETESTER AVEC UN SET_LEVEL POUR DEBUG
logger.error('mon erreur')
Exemplo n.º 8
0
def run_from_args(parser, args, cfg):
    Logger.apply_class_argparser_options(args)
    app = Application()
    app.register_plugin(SonatPlugin)
    return app.run(args)