1.3.3.7. Base classes and utilities

See also the api documentations:

1.3.3.7.1. Introduction

As the api documentation states, the vacumm.misc.bases.Object provides convenient features for logging and configuration management.

This tutorial aims to show you these features and how to customize though most users will only need to use them as such.

In this tutorial we will define a ‘MyObject’ class which inherit from vacumm.misc.bases.Object as you will do in your code to take advantage of this class.

One of the mecanism of this class is to allow developpers to execute things when the class is defined (when its module is imported). The goal of these things may be various but the default goal is to allow a class to be parametrized using a configuration file.

If you also need to do things at class initialization, below is the way you may redefine the vacumm.misc.bases.Object.init_class() method:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from vacumm.misc.bases import Object

class MyObject(Object):
    
    @classmethod
    def init_class(cls, name, bases, dct):
        # do your class related stuff here
        print 'stack trace:'
        print cls.stack_trace()
        print
        print 'default config:'
        print cls.get_default_config()
    
    def __init__(self):
        Object.__init__(self)
        # ...

def main():
    o = MyObject()

if __name__ == '__main__':
    main()

/local/tmp/sraynaud/miniconda2/lib/python2.7/site-packages/cmocean/tools.py:76: MatplotlibDeprecationWarning: The is_string_like function was deprecated in version 2.1.
  if not mpl.cbook.is_string_like(rgbin[0]):
stack trace:
Stack trace (innermost last):
 <module>                                                      (../../../scripts/tutorials/misc.bases.py:6)
 MyObject.__init__                                             (/home/shom/sraynaud/HOM/sraynaud/vacumm-fixes/lib/python/vacumm/misc/bases.py:328)
 MyObject._init_class                                          (/home/shom/sraynaud/HOM/sraynaud/vacumm-fixes/lib/python/vacumm/misc/bases.py:454)
 MyObject.init_class                                           (../../../scripts/tutorials/misc.bases.py:12)

default config:
{'cfg_debug': False, 'log_obj_stats': False}

1.3.3.7.2. Logging features

The vacumm.misc.bases.Object and its instances have logging capabilities through two vacumm.misc.log.Logger objects, one used when logging methods are called from the class and another one dedicated to each instance of this class.

The following example introduce the basic usage of these features.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from vacumm.misc.bases import Object

class MyObject(Object):
    pass

def main():
    
    MyObject.debug('Call logging classmethod debug')
    MyObject.info('Call logging classmethod info')
    MyObject.set_loglevel('debug')
    MyObject.get_logger().set_format('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    MyObject.debug('Call logging classmethod debug')
    
    # Show the default logging configuration
    obj = MyObject()
    obj.notice(
        'These are the default instance logging configuration:\n'
        '  level:       %r (%r)\n'
        '  format:      %r\n'
        '  date format: %r',
        obj.get_loglevel(), obj.get_logger().get_level(),
        obj.get_logger().get_format(),
        obj.get_logger().get_date_format()
    )
    
    # Configure logging at init
    obj = MyObject(
        logger_level='debug',
        logger_format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        logger_date_format='%H:%M:%S'
    )
    obj.notice(
        'Customizing instance logging configuration:\n'
        '  level:       %r (%r)\n'
        '  format:      %r\n'
        '  date format: %r',
        obj.get_loglevel(), obj.get_logger().get_level(),
        obj.get_logger().get_format(),
        obj.get_logger().get_date_format()
    )
    
    obj.debug('debug message')
    obj.verbose('verbose message')
    obj.info('info message')
    obj.notice('notice message')
    obj.warning('warning message')
    obj.error('error message')
    obj.critical('critical message')
    try: 0 / 0
    except Exception, e:
        obj.exception('Division by 0 failed !')
    
    MyObject.verbose('\n  MyObject.get_logger(): %r\n  MyObject.get_class_logger(): %r\n  MyObject.logger: %r', MyObject.get_logger(), MyObject.get_class_logger(), MyObject.logger)
    obj.verbose('\n  obj.get_logger(): %r\n  obj.get_class_logger(): %r\n  obj.logger: %r', obj.get_logger(), obj.get_class_logger(), obj.logger)
    
    obj.set_loglevel('notice')
    obj.notice('the loglevel is now %r', obj.get_loglevel())
    obj.verbose(
        'you will not see this message as it is emitted with a "verbose" level and '
        'the logger is now configured with a %r minimum level', obj.get_loglevel())
    
    obj.notice('Using the config method:\n  obj.logger.config(): %s\n  MyObject().logger.config(): %s\n  MyObject(logger_config=obj).logger.config(): %s',
        obj.logger.config(), MyObject().logger.config(), MyObject(logger_config=obj).logger.config())

if __name__ == '__main__':
    main()

/local/tmp/sraynaud/miniconda2/lib/python2.7/site-packages/cmocean/tools.py:76: MatplotlibDeprecationWarning: The is_string_like function was deprecated in version 2.1.
  if not mpl.cbook.is_string_like(rgbin[0]):
[MyObject INFO] Call logging classmethod info
2018-06-28 13:54:48 CEST - MyObject - DEBUG - Call logging classmethod debug
[MyObject NOTICE] These are the default instance logging configuration:
  level:       'INFO' (20)
  format:      '[%(asctime)s %(name)s %(levelname)s] %(message)s'
  date format: '%Y-%m-%d %H:%M:%S %Z'
[MyObject NOTICE] Customizing instance logging configuration:
  level:       'DEBUG' (10)
  format:      '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  date format: '%H:%M:%S'
[MyObject DEBUG] debug message
[MyObject VERBOSE] verbose message
[MyObject INFO] info message
[MyObject NOTICE] notice message
[MyObject WARNING] warning message
[MyObject ERROR] error message
[MyObject CRITICAL] critical message
[MyObject ERROR] Division by 0 failed !
Traceback (most recent call last):
  File "../../../scripts/tutorials/misc.bases.logging.py", line 52, in main
    try: 0 / 0
ZeroDivisionError: integer division or modulo by zero
2018-06-28 13:54:48 CEST - MyObject - VERBOSE - 
  MyObject.get_logger(): <vacumm.misc.log.Logger object at 0x7f4f6e07ea90>
  MyObject.get_class_logger(): <vacumm.misc.log.Logger object at 0x7f4f6e07ea90>
  MyObject.logger: <property object at 0x7f4f6d4bd838>
[MyObject VERBOSE] 
  obj.get_logger(): <vacumm.misc.log.Logger object at 0x7f4f6d4c5710>
  obj.get_class_logger(): <vacumm.misc.log.Logger object at 0x7f4f6e07ea90>
  obj.logger: <vacumm.misc.log.Logger object at 0x7f4f6d4c5710>
[MyObject NOTICE] the loglevel is now 'NOTICE'
[MyObject NOTICE] Using the config method:
  obj.logger.config(): {'date_format': '%H:%M:%S', 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', 'level': 25}
  MyObject().logger.config(): {'date_format': '%Y-%m-%d %H:%M:%S %Z', 'format': '[%(asctime)s %(name)s %(levelname)s] %(message)s', 'level': 20}
  MyObject(logger_config=obj).logger.config(): {'date_format': '%H:%M:%S', 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', 'level': 25}

1.3.3.7.3. Configuration features

vacumm.misc.bases.Object may use configurations features provided by the config module.

When your module containing the vacumm.misc.bases.Object subclass is loaded, a specification file with the module name with the ‘.py’ extension replaced with the ‘.ini’ extension is looked up and the default configuration is loaded from the specification defaults.

The way you may change your class’s specification file is to redefine the vacumm.misc.bases.Object.get_config_spec_file() method, the section name defaults to the class name, you may also change it by redefining the vacumm.misc.bases.Object.get_config_section_name() method.

The vacumm.misc.bases.Object.get_default_config() method will return the default configuration mentionned above.

The vacumm.misc.bases.Object.apply_config() method will be called each time a configuration is loaded using the vacumm.misc.bases.Object.load_config() method, you may also want to redefine.

Logging configuration related to your class may be customized using a nested section named ‘Logger’.

Specification file:

[MyObject]
    afloat = float(default=0.0)
    somefloats = floats(default=list())

Configuration file:

[MyObject]
    afloat = 42.0
    somefloats = 0, 42.0
    [[Logger]]
        level = 'debug'
        format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        date_format = '%H:%M:%S'
    # Warning:
    # Despite indentation, any options that appear below will be loaded as 
    # Logger subsection option... So you have to always write subsections after
    # a section options

Code using these specification and configuration:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os, sys

from vacumm.misc.bases import Object

class MyObject(Object):
    # These are the defaults but in case you want to change these behavior...
    @classmethod
    def get_config_spec_file(cls):
        return os.path.splitext(__file__)[0] + '.ini'
        # => misc.bases.config.ini
    @classmethod
    def get_config_section_name(cls):
        # => MyObject
        return cls.__name__

def main():
    
    obj = MyObject()
    cfg = obj.get_default_config()
    obj.info('Default configuration:\n%s', obj.pformat(cfg.dict()))
    cfgfile = os.path.splitext(__file__)[0] + '.cfg'
    cfg = obj.load_config(cfgfile)
    obj.info('Loaded configuration (%s):\n%s', cfgfile, obj.pformat(cfg.dict()))
    obj.debug('Debug message after configuration has been loaded')

if __name__ == '__main__':
    main()

The outputs of this code are:

/local/tmp/sraynaud/miniconda2/lib/python2.7/site-packages/cmocean/tools.py:76: MatplotlibDeprecationWarning: The is_string_like function was deprecated in version 2.1.
  if not mpl.cbook.is_string_like(rgbin[0]):
[MyObject INFO] Default configuration:
{ 'afloat': 0.0, 'cfg_debug': False, 'log_obj_stats': False, 'somefloats': []}
[MyObject INFO] Loaded configuration (../../../scripts/tutorials/misc.bases.config.cfg):
{ 'MyObject': { 'Logger': { 'date_format': '%H:%M:%S',
                            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                            'level': 'debug'},
                'afloat': '42.0',
                'somefloats': ['0', '42.0']},
  'afloat': 0.0,
  'cfg_debug': False,
  'log_obj_stats': False,
  'somefloats': []}

Note that MyObject attributes and logger have been changed by the loaded configuration.

1.3.3.7.4. Debugging features

The following example shows some method calls you may use when writing your code to trace or debug it.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cdms2, numpy

from vacumm.misc.axes import create_time, create_lat, create_lon
from vacumm.misc.bases import Object

class MyObject(Object):
    def do_something(self):
        num, den = 0, 0
        try: num / den
        except: self.exception('Division by 0 failed !\n%s', self.exception_trace())
        self.info('The function emitting this message is: %s', self.func_name())
        self.info('The stack trace of the function emitting this message is:\n%s', self.stack_trace())
        t = create_time(['1900-01-01 00:00:00', '9999-12-31 23:59:59'])
        y = create_lat(range(-90, 90))
        x = create_lon(range(-180, 180))
        v = cdms2.createVariable(numpy.random.ranf((len(t), len(y), len(x))), axes=[t, y, x], id='data', long_name='random data')
        self.info('Time:\n%s', self.describe(t))
        self.info('Latitude:\n%s', self.describe(y))
        self.info('Longitude:\n%s', self.describe(x, stats=True))
        self.info('Variable:\n%s', self.describe(v, stats=True))

def main():
    obj = MyObject()
    obj.do_something()

if __name__ == '__main__':
    main()

/local/tmp/sraynaud/miniconda2/lib/python2.7/site-packages/cmocean/tools.py:76: MatplotlibDeprecationWarning: The is_string_like function was deprecated in version 2.1.
  if not mpl.cbook.is_string_like(rgbin[0]):
[MyObject ERROR] Division by 0 failed !
--------------------------------------------------------------------------------
Exception details, locals by frame, innermost last
--------------------------------------------------------------------------------
  File "../../../scripts/tutorials/misc.bases.debug.py", line 30, in <module>
    main()

	            MyObject <class 'vacumm.misc.bases.Class'> = <class '__main__.MyObject'>
	              Object <class 'vacumm.misc.bases.Class'> = <class 'vacumm.misc.bases.Object'>

--------------------------------------------------------------------------------
  File "../../../scripts/tutorials/misc.bases.debug.py", line 27, in main
    obj.do_something()

	                 obj <class '__main__.MyObject'> = <__main__.MyObject object at 0x7ff8d89565d0>

--------------------------------------------------------------------------------
  File "../../../scripts/tutorials/misc.bases.debug.py", line 13, in do_something
    except: self.exception('Division by 0 failed !\n%s', self.exception_trace())

	                 den <type 'int'> = 0
	                 num <type 'int'> = 0
	                self <class '__main__.MyObject'> = <__main__.MyObject object at 0x7ff8d89565d0>

--------------------------------------------------------------------------------
Traceback (most recent call last):
  File "../../../scripts/tutorials/misc.bases.debug.py", line 12, in do_something
    try: num / den
ZeroDivisionError: integer division or modulo by zero

--------------------------------------------------------------------------------
Traceback (most recent call last):
  File "../../../scripts/tutorials/misc.bases.debug.py", line 12, in do_something
    try: num / den
ZeroDivisionError: integer division or modulo by zero
[MyObject INFO] The function emitting this message is: do_something
[MyObject INFO] The stack trace of the function emitting this message is:
Stack trace (innermost last):
 <module>                                                      (../../../scripts/tutorials/misc.bases.debug.py:30)
 main                                                          (../../../scripts/tutorials/misc.bases.debug.py:27)
 MyObject(140706762286544).do_something                        (../../../scripts/tutorials/misc.bases.debug.py:15)
[MyObject INFO] Time:
TransientAxis: time, size: 2
[MyObject INFO] Latitude:
TransientAxis: lat, size: 180
[MyObject INFO] Longitude:
TransientAxis: lon, size: 360, min: -180, max: 179, avg: -0.5, count: 360
[MyObject INFO] Variable:
TransientVariable: data, shape: (time=2,lat=180,lon=360), order: tyx, min: 2.72602279107e-05, max: 0.999987298231, avg: 0.499345835766, count: 129600