"""This module contains classes and functions useful for pretty/color printing to console or logs
it is named after `Delphi <http://en.wikipedia.org/wiki/Delphi>`_ the famous city where
ancient `Oracle of Delphi <https://en.wikipedia.org/wiki/Pythia>`_ was located.
"""
import sys
import logging
from logging import handlers # in python 3 not in logging.hendlers
import time
from Hellas.Sparta import DotDot
from Hellas import _IS_PY2
try:
import simplejson as anyjson
except ImportError as e:
import json as anyjson
class Color(object):
"""some basic color handling for printing in color
.. Warning:: This class will **NOT** work in windows OS unless complemented by
library `colorama <https://pypi.python.org/pypi/colorama>`_
:Example:
>>> cl = Color()
>>> cl.printc("this is red", "red")
"""
colors = DotDot({
'black': (0, 30), 'gray_br': (0, 37),
'blue': (0, 34), 'white': (1, 37),
'green': (0, 32), 'blue_br': (1, 34),
'cyan': (0, 36), 'green_br': (1, 32),
'red': (0, 31), 'cyan_br': (1, 36),
'purple': (0, 35), 'red_br': (1, 31),
'yellow': (0, 33), 'purple_br': (1, 35),
'gray_dk': (1, 30), 'yellow_br': (1, 33),
'normal': (0,)
})
@classmethod
def help(cls):
"""prints named colors"""
print("for named colors use :")
for c in sorted(list(cls.colors.items())):
print("{:10} {}".format(*c))
@classmethod
def color_code(cls, color):
""" returns code for color
:param tuple_or_code color: either a tuple as in colors.values or a string key to colors dictionary
"""
if not isinstance(color, tuple):
color = cls.colors[color]
return "{:d};{}".format(color[0], str(color[1]) if len(color) == 2 else "")
@classmethod
def color_switch_txt(cls, color=colors.red):
return "\033[{}m".format(cls.color_code(color))
@classmethod
def color_txt(cls, txt="", color=None):
if _IS_PY2 and isinstance(txt, unicode):
txt = txt.encode("utf-8")
return "{}{}\033[0m".format(cls.color_switch_txt(color), txt)
@classmethod
def printc(cls, txt, color=colors.red):
"""Print in color."""
print(cls.color_txt(txt, color))
@classmethod
def color_switch_print(cls, color):
print(cls.color_switch_txt(color))
class ColoredFormatter(logging.Formatter):
"""a logging formatter for printing in color works only in linux
on an non linux system it returns plain text
"""
if sys.platform.startswith('linux'):
color = Color()
clr_name = color.colors
def format(self, record):
levelno = record.levelno
if(levelno >= 50):
clr = self.clr_name.red_br # CRITICAL / FATAL
elif(levelno >= 40):
clr = self.clr_name.red # ERROR
elif(levelno >= 30):
clr = self.clr_name.yellow # WARNING
elif(levelno >= 20):
clr = self.clr_name.green # INFO
elif(levelno >= 10):
clr = self.clr_name.purple_br # DEBUG
else:
clr = self.cls_name.normal # NOTSET etc
return self.color.color_txt(logging.Formatter.format(self, record), clr)
else:
def format(self, record):
return logging.Formatter.format(self, record)
def logging_format(verbose=2, style='txt'):
"""returns a format
:parameter:
- str style: defines style of output format (defaults to txt)
- txt plain text
- dict like text which can be casted to dict
"""
frmt = "'dt':'%(asctime)s', 'lv':'%(levelname)-7s', 'ln':'%(name)s'"
if verbose > 1:
frmt += ",\t'Func': '%(funcName)-12s','line':%(lineno)5d, 'module':'%(module)s'"
if verbose > 2:
frmt += ",\n\t'file':'%(filename)s',\t'Process':['%(processName)s', %(process)d], \
'thread':['%(threadName)s', %(thread)d], 'ms':%(relativeCreated)d"
frmt += ",\n\t'msg':'%(message)s'"
if style == "dict":
frmt = "{" + frmt + "}"
frmt = frmt.replace(" ", "").replace("\n", "").replace("\t", "")
if style == "txt":
frmt = frmt.replace("'", "").replace(",", "")
return frmt
class LoggingColorHandler(logging.StreamHandler):
def __init__(self, level=logging.NOTSET, verbose=2):
super(LoggingColorHandler, self).__init__()
self.setLevel(level)
formatterC = ColoredFormatter(logging_format(verbose, 'str'))
formatterC.converter = time.gmtime
self.setFormatter(formatterC)
def logger_multi(
loggerName="", # top
level_consol=logging.DEBUG,
level_file=logging.DEBUG,
filename=None,
verbose=1,
when='midnight',
interval=1,
backupCount=7):
"""a logger that logs to file as well as as screen
see http://pythonhosted.org//logutils/ http://plumberjack.blogspot.gr/2010/10/supporting-alternative-formatting.html
:Todo:
- use new style formating for python > v3 i.e formatter = logging.Formatter(frmt.replace(" ", ""), style='{')
#frmtC = frmt.translate(dict((ord(c), '') for c in "'{},"))
:Parameters: `see <https://docs.python.org/2/library/logging.html#module-logging>`_
:Example:
>>> LOG = logger_double('', level_consol=logging.DEBUG, level_file=logging.DEBUG, verbose=3, filename="~\f.log")
"""
logger = logging.getLogger(loggerName)
logger.setLevel(min(level_consol if level_consol else 100, level_file if level_file else 100))
# logger.disable_existing_loggers = False
if level_file:
if filename is None:
filename = "~\py.log"
formatter = logging.Formatter(logging_format(verbose, 'str'))
formatter.converter = time.gmtime
hf = handlers.TimedRotatingFileHandler(
filename, when=when, interval=interval,
backupCount=backupCount, encoding='utf-8', delay=False, utc=True)
hf.setFormatter(formatter)
hf.setLevel(level_file)
logger.addHandler(hf)
if level_consol:
logger.addHandler(LoggingColorHandler(level=level_consol, verbose=verbose))
return logger
def auto_retry(exception_t, retries=3, sleepSeconds=1, back_of_factor=1, logger_fun=None):
"""a generic auto-retry function @wrapper
:param Exception exception_t: exception (or tuple of exceptions) to auto retry
:param int retries: max retries before it raises the Exception (defaults to 3)
:param int_or_float sleepSeconds: base sleep seconds between retries (defaults to 1)
:param int back_of_factor: factor to back off on each retry (defaults to 1)
:param int logger_fun: loggerFun i.e. logger.info to log on each retry (defaults to None)
"""
def wrapper(func):
def fun_call(*args, **kwargs):
tries = 0
while tries < retries:
try:
return func(*args, **kwargs)
except exception_t as e:
tries += 1
if logger_fun:
logger_fun("exception [%s] e=[%s] handled tries :%d sleeping[%f]" %
(exception_t, e, tries, sleepSeconds * tries * back_of_factor))
time.sleep(sleepSeconds * tries * back_of_factor)
raise
return fun_call
return wrapper
def pp_obj(obj, indent=4, sort_keys=False, prn=True, default=None):
"""pretty prints a (list tuple or dict) object
"""
assert isinstance(obj, (list, tuple, dict))
rt = anyjson.dumps(obj, sort_keys=sort_keys, indent=indent,
separators=(',', ': '), default=default, namedtuple_as_object=False)
if prn:
print(rt)
else:
return rt