"""
Python utilities for data processing of .crw/.log/.arf files.
This __init__.py file in particular implements some logging facilities that can
be used throughout mkpy.
"""
import logging
import functools
from datetime import datetime
from pathlib import Path
import sys
import traceback
from pprint import pformat
import re
# from . import dpath
__version__ = "0.2.7"
[docs]def get_ver():
# check semantic version format in __init__.py and meta.yaml matches
pf_ver = re.search(r"(?P<ver_str>\d+\.\d+\.\d+\S*)", __version__)
if pf_ver is None:
msg = f"""Illegal __version__: {__version__}
spudtr __init__.py must have an X.Y.Z semantic version, e.g.,
__version__ = '0.0.0'
__version__ = '0.0.0.dev0'
__version__ = '0.0.0rc1'
"""
raise Exception(msg)
ver_str = pf_ver["ver_str"]
return ver_str
# log names are timestamps
current_datetime = datetime.now().strftime("%m-%d-%y_%H:%M:%S")
# .mkpy in the home dir is used to store mkpy-specific files
home_dir = Path.home()
base_dir = home_dir.joinpath(".mkpy")
Path(base_dir).mkdir(parents=True, exist_ok=True)
# declare subdirectories here
log_dir = base_dir.joinpath("logs")
# create subdirectories here
Path(log_dir).mkdir(parents=True, exist_ok=True)
# .log files will be put in ~/.mkpy/logs and have timestamps for filenames
log_filename = log_dir.joinpath(current_datetime).with_suffix(".log")
logging.basicConfig(filename=log_filename, format="%(message)s", level=logging.DEBUG)
[docs]def current_function():
"""Returns the name of the calling function.
Example:
def caller():
print(current_function())
>>> caller()
'caller'
"""
return sys._getframe(1).f_code.co_name
[docs]def indent(level, text):
"""Returns text with each line indented 'level' number of times."""
fstring = level * "\t" + "{}"
return "".join([fstring.format(l) for l in text.splitlines(True)])
[docs]def log_exceptions(indent_level):
"""This decorator turns on exception logging for a wrapped function.
Examples
.. code-block:: python
@log_exceptions()
def function():
return 0
This will turn on exception logging and the traceback in the log file will
be indented with one tab. To specify deeper levels of indentation, use the
indent_level parameter:
.. code-block:: python
@log_exceptions(indent_level=2)
def function2():
return 0
"""
def inner_decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
logging.info(
indent(level=indent_level, text="FUNCTION " + function.__name__)
)
try:
return function(*args, **kwargs)
except:
# log the traceback
msg = "Exception in " + function.__name__ + ":\n"
msg += traceback.format_exc()
logging.critical(indent(level=indent_level + 1, text=msg))
# if DEBUG (or lower) logging level set, log the arguments
logging.debug(
indent(level=indent_level + 1, text="Positional arguments:")
)
for i, arg in enumerate(args):
text = "Argument " + str(i) + ":"
logging.debug(indent(level=indent_level + 2, text=text))
logging.debug(indent(level=indent_level + 3, text=pformat(arg)))
if kwargs:
logging.debug(
indent(level=indent_level + 1, text="Keyword arguments:")
)
for kwarg in kwargs:
text = kwarg + ":"
logging.debug(indent(level=indent_level + 2, text=text))
logging.debug(
indent(level=indent_level + 3, text=pformat(kwarg))
)
# reraise the original exception
raise
return wrapper
return inner_decorator