1.3.3.7. Base classes and utilities¶
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