Convert settings.py to settings.yml

This commit is contained in:
Wojciech Kozlowski 2019-07-31 20:08:05 +02:00
parent da5a114bc1
commit 20e0c3219c
11 changed files with 1239 additions and 807 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
dist
PyLg.egg-info
*.log
.coverage

View File

@ -1,24 +1,8 @@
# -----------------------------------------------------------------------------
# PyLg: module to facilitate and automate the process of writing runtime logs.
# Copyright (C) 2017 Wojciech Kozlowski <wk@wojciechkozlowski.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
"""PyLg: facilitate and automate the process of writing runtime logs.
from .load_settings import PYLG_ENABLE
"""
if PYLG_ENABLE:
from .pylg import TraceFunction, trace
else:
from .dummy import TraceFunctionDummy as TraceFunction, trace
from pylg.pylg import TraceFunction, trace
assert TraceFunction
assert trace

View File

@ -1,355 +0,0 @@
# -----------------------------------------------------------------------------
# PyLg: module to facilitate and automate the process of writing runtime logs.
# Copyright (C) 2017 Wojciech Kozlowski <wk@wojciechkozlowski.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
import traceback
import warnings
import inspect
import sys
import os
# -----------------------------------------------------------------------------
# Import all the defaults first.
# -----------------------------------------------------------------------------
from .settings import *
# -----------------------------------------------------------------------------
# The filename of the user settings. It will be set once it can be
# determined after loading the module.
# -----------------------------------------------------------------------------
PYLG_USER_FILE = None
# -----------------------------------------------------------------------------
# Attempt to load the module pylg_settings module. If successful, set
# PYLG_USER_FILE to the module's path. By attempting an import rather
# than checking if a file exists, we can handle the case of the user
# having the settings file elsewhere in their path.
# -----------------------------------------------------------------------------
try:
# -------------------------------------------------------------------------
# We import the module itself to be able to determine its source
# file before we import all the settings.
# -------------------------------------------------------------------------
import pylg_settings
from pylg_settings import *
PYLG_USER_FILE = inspect.getsourcefile(pylg_settings)
except ImportError:
# -------------------------------------------------------------------------
# The user settings don't exist. We assume the user is happy with
# the defaults.
# -------------------------------------------------------------------------
pass
except (NameError, SyntaxError):
warnings.warn("There was a problem importing user settings")
sys.stderr.write("\n")
traceback.print_exc(file=sys.stderr)
sys.stderr.write("\n")
# -----------------------------------------------------------------------------
# Utility functions for sanity checking user settings. They raise an
# ImportError if something is wrong.
# -----------------------------------------------------------------------------
def pylg_check_bool(value, name):
if not isinstance(value, bool):
warning_msg = ("Invalid type for " + name + " in " +
PYLG_USER_FILE +
" - should be bool, is type " +
type(value).__name__)
warnings.warn(warning_msg)
raise ImportError
def pylg_check_string(value, name):
if not isinstance(value, str):
warning_msg = ("Invalid type for " + name + " in " +
PYLG_USER_FILE +
" - should be a string, is type " +
type(value).__name__)
warnings.warn(warning_msg)
raise ImportError
def pylg_check_int(value, name):
# -------------------------------------------------------------------------
# We check for bool as well as bools are an instance of int, but
# we don't want to let that go through.
# -------------------------------------------------------------------------
if not isinstance(value, int) or isinstance(value, bool):
warning_msg = ("Invalid type for " + name + " in " +
PYLG_USER_FILE +
" - should be int, is " +
type(value).__name__)
warnings.warn(warning_msg)
raise ImportError
def pylg_check_nonneg_int(value, name):
pylg_check_int(value, name)
if value < 0:
warning_msg = ("Invalid value for " + name + " in " +
PYLG_USER_FILE +
" - should be non-negative, is " +
str(value))
warnings.warn(warning_msg)
raise ImportError
def pylg_check_pos_int(value, name):
pylg_check_int(value, name)
if value <= 0:
warning_msg = ("Invalid value for " + name + " in " +
PYLG_USER_FILE +
" - should be positive, is " +
str(value))
warnings.warn(warning_msg)
raise ImportError
if PYLG_USER_FILE is not None:
# -------------------------------------------------------------------------
# If PYLG_USER_FILE is set, we have successfully imported user
# settings. Nowe, we need to sanity check them. If anything is
# wrong we reset the value to its default. At this stage a single
# error should not affect any other settings.
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
# PYLG_ENABLE - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(PYLG_ENABLE, "PYLG_ENABLE")
except ImportError:
from .settings import PYLG_ENABLE
# -------------------------------------------------------------------------
# PYLG_FILE - string
# -------------------------------------------------------------------------
try:
pylg_check_string(PYLG_FILE, "PYLG_FILE")
except ImportError:
from .settings import PYLG_FILE
# -------------------------------------------------------------------------
# DEFAULT_EXCEPTION_WARNING - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(DEFAULT_EXCEPTION_WARNING, "DEFAULT_EXCEPTION_WARNING")
except ImportError:
from .settings import DEFAULT_EXCEPTION_WARNING
# -------------------------------------------------------------------------
# DEFAULT_EXCEPTION_TB_FILE - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(DEFAULT_EXCEPTION_TB_FILE, "DEFAULT_EXCEPTION_TB_FILE")
except ImportError:
from .settings import DEFAULT_EXCEPTION_TB_FILE
# -------------------------------------------------------------------------
# DEFAULT_EXCEPTION_TB_STDERR - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(DEFAULT_EXCEPTION_TB_STDERR,
"DEFAULT_EXCEPTION_TB_STDERR")
except ImportError:
from .settings import DEFAULT_EXCEPTION_TB_STDERR
# -------------------------------------------------------------------------
# DEFAULT_EXCEPTION_EXIT - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(DEFAULT_EXCEPTION_EXIT, "DEFAULT_EXCEPTION_EXIT")
except ImportError:
from .settings import DEFAULT_EXCEPTION_EXIT
# -------------------------------------------------------------------------
# TRACE_TIME - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(TRACE_TIME, "TRACE_TIME")
except ImportError:
from .settings import TRACE_TIME
# -------------------------------------------------------------------------
# TIME_FORMAT - string
# -------------------------------------------------------------------------
try:
pylg_check_string(TIME_FORMAT, "TIME_FORMAT")
except ImportError:
from .settings import TIME_FORMAT
# -------------------------------------------------------------------------
# TRACE_FILENAME - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(TRACE_FILENAME, "TRACE_FILENAME")
except ImportError:
from .settings import TRACE_FILENAME
# -------------------------------------------------------------------------
# FILENAME_COLUMN_WIDTH - non-negative integer
# -------------------------------------------------------------------------
try:
pylg_check_pos_int(FILENAME_COLUMN_WIDTH, "FILENAME_COLUMN_WIDTH")
except ImportError:
from .settings import FILENAME_COLUMN_WIDTH
# -------------------------------------------------------------------------
# TRACE_LINENO - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(TRACE_LINENO, "TRACE_LINENO")
except ImportError:
from .settings import TRACE_LINENO
# -------------------------------------------------------------------------
# LINENO_WIDTH - non-negative integer
# -------------------------------------------------------------------------
try:
pylg_check_nonneg_int(LINENO_WIDTH, "LINENO_WIDTH")
except ImportError:
from .settings import LINENO_WIDTH
# -------------------------------------------------------------------------
# TRACE_FUNCTION - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(TRACE_FUNCTION, "TRACE_FUNCTION")
except ImportError:
from .settings import TRACE_FUNCTION
# -------------------------------------------------------------------------
# FUNCTION_COLUMN_WIDTH - non-negative integer
# -------------------------------------------------------------------------
try:
pylg_check_pos_int(FUNCTION_COLUMN_WIDTH, "FUNCTION_COLUMN_WIDTH")
except ImportError:
from .settings import FUNCTION_COLUMN_WIDTH
# -------------------------------------------------------------------------
# CLASS_NAME_RESOLUTION - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(CLASS_NAME_RESOLUTION, "CLASS_NAME_RESOLUTION")
except ImportError:
from .settings import CLASS_NAME_RESOLUTION
# -------------------------------------------------------------------------
# TRACE_MESSAGE - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(TRACE_MESSAGE, "TRACE_MESSAGE")
except ImportError:
from .settings import TRACE_MESSAGE
# -------------------------------------------------------------------------
# MESSAGE_WIDTH - non-negative integer - note 0 denotes unlimited
# -------------------------------------------------------------------------
try:
pylg_check_nonneg_int(MESSAGE_WIDTH, "MESSAGE_WIDTH")
except ImportError:
from .settings import MESSAGE_WIDTH
# -------------------------------------------------------------------------
# MESSAGE_WRAP - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(MESSAGE_WRAP, "MESSAGE_WRAP")
except ImportError:
from .settings import MESSAGE_WRAP
# -------------------------------------------------------------------------
# MESSAGE_MARK_TRUNCATION - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(MESSAGE_MARK_TRUNCATION, "MESSAGE_MARK_TRUNCATION")
except ImportError:
from .settings import MESSAGE_MARK_TRUNCATION
# -------------------------------------------------------------------------
# TRACE_SELF - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(TRACE_SELF, "TRACE_SELF")
except ImportError:
from .settings import TRACE_SELF
# -------------------------------------------------------------------------
# COLLAPSE_LISTS - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(COLLAPSE_LISTS, "COLLAPSE_LISTS")
except ImportError:
from .settings import COLLAPSE_LISTS
# -------------------------------------------------------------------------
# COLLAPSE_DICTS - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(COLLAPSE_DICTS, "COLLAPSE_DICTS")
except ImportError:
from .settings import COLLAPSE_DICTS
# -------------------------------------------------------------------------
# DEFAULT_TRACE_ARGS - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(DEFAULT_TRACE_ARGS, "DEFAULT_TRACE_ARGS")
except ImportError:
from .settings import DEFAULT_TRACE_ARGS
# -------------------------------------------------------------------------
# DEFAULT_TRACE_RV - bool
# -------------------------------------------------------------------------
try:
pylg_check_bool(DEFAULT_TRACE_RV, "DEFAULT_TRACE_RV")
except ImportError:
from .settings import DEFAULT_TRACE_RV
# -----------------------------------------------------------------------------
# Some final value processing.
# -----------------------------------------------------------------------------
if MESSAGE_WIDTH == 0:
MESSAGE_WIDTH = float("inf")

View File

@ -1,24 +1,8 @@
# -----------------------------------------------------------------------------
# PyLg: module to facilitate and automate the process of writing runtime logs.
# Copyright (C) 2017 Wojciech Kozlowski <wk@wojciechkozlowski.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
"""PyLg: facilitate and automate the process of writing runtime logs."""
from __future__ import print_function
from datetime import datetime
from functools import partial
from typing import Optional
import traceback
import warnings
import textwrap
@ -26,103 +10,156 @@ import inspect
import sys
import os
# -----------------------------------------------------------------------------
# Load settings.
# -----------------------------------------------------------------------------
from .load_settings import *
from pylg.settings import _pylg_check_bool
import pylg.settings
class ClassNameStack(object):
class ClassNameStack:
"""Stack for the class names of the currently executing functions.
The class name of the last traced function that was called will be on top
of the stack. It is removed after it finishes executing.
""" A class to keep a global stack of the class names of the
functions that are currently executing. The class name of the
last traced function that was called will be on top of the
stack. It is removed after it finishes executing.
"""
stack = []
@staticmethod
def insert(classname):
if CLASS_NAME_RESOLUTION:
ClassNameStack.stack.append(classname)
@classmethod
def disable(cls) -> None:
"""Disable the stack.
@staticmethod
def pop():
if CLASS_NAME_RESOLUTION and ClassNameStack.stack:
ClassNameStack.stack.pop()
This is achieved by rendering all functions no-ops.
@staticmethod
def get():
if CLASS_NAME_RESOLUTION and ClassNameStack.stack:
return ClassNameStack.stack[-1]
else:
return None
WARNING: This is an irreversible operation.
class PyLg(object):
""" Class to handle the log file.
"""
cls.insert = lambda _classname: None
cls.pop = lambda: None
cls.get = lambda: None
@classmethod
def insert(cls, classname: str) -> None:
"""Insert an entry.
Parameters
----------
classname : str
The class name to insert.
"""
cls.stack.append(classname)
@classmethod
def pop(cls) -> str:
"""str: Return top-most entry and remove it."""
return cls.stack.pop() if cls.stack else None
@classmethod
def peek(cls) -> str:
"""str: Return the top-most entry without removing it."""
return ClassNameStack.stack[-1] if cls.stack else None
class PyLg:
"""Class to handle the log file."""
wfile = None
filename = PYLG_FILE
@staticmethod
def set_filename(new_filename):
@classmethod
def configure(cls, user_settings_path: Optional[str] = None) -> None:
"""PyLg initialisation.
""" Change the file name of the log file. The change will be
rejected if the log file is already open.
Parameters
----------
user_settings_path : Optional[str]
Path to the user settings file.
:param str new_filename: The new file name for the log file.
"""
if PyLg.wfile is None:
PyLg.filename = new_filename
else:
warnings.warn("PyLg wfile is open - cannot change filename")
# ---------------------------------------------------------------------
# Load the settings.
# ---------------------------------------------------------------------
settings = pylg.settings.load(user_settings_path)
@staticmethod
def write(string):
# ---------------------------------------------------------------------
# Local variables for settings to avoid dictionary access which is only
# O(1) on average. Admittedly the size of the settings dict is not
# large, but these accesses will be frequent.
# ---------------------------------------------------------------------
cls.pylg_enable = settings["pylg_enable"]
cls.pylg_file = settings["pylg_file"]
cls.default_exception_warning = settings["default_exception_warning"]
cls.default_exception_tb_file = settings["default_exception_tb_file"]
cls.default_exception_tb_stderr = \
settings["default_exception_tb_stderr"]
cls.default_exception_exit = settings["default_exception_exit"]
cls.trace_time = settings["trace_time"]
cls.time_format = settings["time_format"]
cls.trace_filename = settings["trace_filename"]
cls.filename_column_width = settings["filename_column_width"]
cls.trace_lineno = settings["trace_lineno"]
cls.lineno_width = settings["lineno_width"]
cls.trace_function = settings["trace_function"]
cls.function_column_width = settings["function_column_width"]
cls.class_name_resolution = settings["class_name_resolution"]
cls.trace_message = settings["trace_message"]
cls.message_width = settings["message_width"]
cls.message_wrap = settings["message_wrap"]
cls.message_mark_truncation = settings["message_mark_truncation"]
cls.trace_self = settings["trace_self"]
cls.collapse_lists = settings["collapse_lists"]
cls.collapse_dicts = settings["collapse_dicts"]
cls.default_trace_args = settings["default_trace_args"]
cls.default_trace_rv = settings["default_trace_rv"]
cls.default_trace_rv_type = settings["default_trace_rv_type"]
""" Write to the log file. A new log file is opened and
initialised if it has not been opened yet.
if not cls.class_name_resolution:
ClassNameStack.disable()
cls.wfile = open(cls.pylg_file, "w")
cls.wfile.write(
"=== Log initialised at {} ===\n\n".format(datetime.now())
)
@classmethod
def write(cls, string: str):
"""Write to the log file.
A new log file is opened and initialised if it has not been opened yet.
Parameters
----------
string : str
The string to be written to the log file.
:param str string: The string to be written to the log file.
"""
if PyLg.wfile is None:
PyLg.wfile = open(PyLg.filename, "w")
PyLg.wfile.write("=== Log initialised at " +
str(datetime.now()) + " ===\n\n")
cls.wfile.write(string)
cls.wfile.flush()
PyLg.wfile.write(string)
PyLg.wfile.flush()
@classmethod
def close(cls):
"""Close the log file."""
@staticmethod
def close():
""" Close the log file.
"""
if PyLg.wfile is not None:
PyLg.wfile.close()
PyLg.wfile = None
if cls.wfile is not None:
cls.wfile.close()
cls.wfile = None
else:
warnings.warn("PyLg wfile is not open - nothing to close")
class TraceFunction(object):
# pylint: disable=too-many-instance-attributes
class TraceFunction:
"""Decorator to trace entry and exit from functions.
Used by appending @TraceFunction on top of the definition of the function
to trace.
""" Class that serves as a decorator to trace entry and exit from
functions. Used by appending @TraceFunction on top of the
definition of the function to trace.
"""
class TraceFunctionStruct(object):
""" Internal object to handle traced function properties.
"""
# pylint: disable=too-few-public-methods
class TraceFunctionStruct:
"""Internal object to handle traced function properties."""
function = None
varnames = None
@ -134,19 +171,10 @@ class TraceFunction(object):
functionname = None
def __get__(self, obj, objtype):
""" Support for instance functions.
"""
"""Support for instance functions."""
return partial(self.__call__, obj)
def __init__(self, *args, **kwargs):
""" Constructor for TraceFunction. Note that the behaviour is
different depending on whether TraceFunction is passed any
parameters. For details see __call__ in this class.
"""
# ---------------------------------------------------------------------
# Make sure this decorator is never called with no arguments.
# ---------------------------------------------------------------------
@ -154,14 +182,14 @@ class TraceFunction(object):
if args:
self.exception_warning = DEFAULT_EXCEPTION_WARNING
self.exception_tb_file = DEFAULT_EXCEPTION_TB_FILE
self.exception_tb_stderr = DEFAULT_EXCEPTION_TB_STDERR
self.exception_exit = DEFAULT_EXCEPTION_EXIT
self._exception_warning = PyLg.default_exception_warning
self._exception_tb_file = PyLg.default_exception_tb_file
self._exception_tb_stderr = PyLg.default_exception_tb_stderr
self._exception_exit = PyLg.default_exception_exit
self.trace_args = DEFAULT_TRACE_ARGS
self.trace_rv = DEFAULT_TRACE_RV
self.trace_rv_type = DEFAULT_TRACE_RV_TYPE
self._trace_args = PyLg.default_trace_args
self._trace_rv = PyLg.default_trace_rv
self._trace_rv_type = PyLg.default_trace_rv_type
# -----------------------------------------------------------------
# The function init_function will verify the input.
@ -170,6 +198,11 @@ class TraceFunction(object):
if kwargs:
# -----------------------------------------------------------------
# If kwargs is non-empty, args should be empty.
# -----------------------------------------------------------------
assert not args
exception_warning_str = 'exception_warning'
exception_tb_file_str = 'exception_tb_file'
exception_tb_stderr_str = 'exception_tb_stderr'
@ -179,70 +212,47 @@ class TraceFunction(object):
trace_rv_str = 'trace_rv'
trace_rv_type_str = 'trace_rv_type'
# -----------------------------------------------------------------
# If kwargs is non-empty, args should be empty.
# -----------------------------------------------------------------
assert not args
kwopts = {}
for option, default in [
(exception_warning_str, PyLg.default_exception_warning),
(exception_tb_file_str, PyLg.default_exception_tb_file),
(exception_tb_stderr_str,
PyLg.default_exception_tb_stderr),
(exception_exit_str, PyLg.default_exception_exit),
(trace_args_str, PyLg.default_trace_args),
(trace_rv_str, PyLg.default_trace_rv),
(trace_rv_type_str, PyLg.default_trace_rv_type),
]:
try:
self.exception_warning = kwargs[exception_warning_str]
pylg_check_bool(self.exception_warning, "exception_warning")
except (KeyError, ImportError):
self.exception_warning = DEFAULT_EXCEPTION_WARNING
kwopts[option] = kwargs.get(option, default)
if not _pylg_check_bool(kwopts[option]):
raise ValueError(
"Invalid type for {} - should be bool, is type {}"
.format(option, type(kwopts[option]).__name__)
)
try:
self.exception_tb_file = kwargs[exception_tb_file_str]
pylg_check_bool(self.exception_tb_file, "exception_tb_file")
except (KeyError, ImportError):
self.exception_tb_file = DEFAULT_EXCEPTION_TB_FILE
self._exception_warning = kwopts[exception_warning_str]
self._exception_tb_file = kwopts[exception_tb_file_str]
self._exception_tb_stderr = kwopts[exception_tb_stderr_str]
self._exception_exit = kwopts[exception_exit_str]
try:
self.exception_tb_stderr = kwargs[exception_tb_stderr_str]
pylg_check_bool(self.exception_tb_stderr,
"exception_tb_stderr")
except (KeyError, ImportError):
self.exception_tb_stderr = DEFAULT_EXCEPTION_TB_STDERR
try:
self.exception_exit = kwargs[exception_exit_str]
pylg_check_bool(self.exception_exit, "exception_exit")
except (KeyError, ImportError):
self.exception_exit = DEFAULT_EXCEPTION_EXIT
try:
self.trace_args = kwargs[trace_args_str]
pylg_check_bool(self.trace_args, "trace_args")
except (KeyError, ImportError):
self.trace_args = DEFAULT_TRACE_ARGS
try:
self.trace_rv = kwargs[trace_rv_str]
pylg_check_bool(self.trace_rv, "trace_rv")
except (KeyError, ImportError):
self.trace_rv = DEFAULT_TRACE_RV
try:
self.trace_rv_type = kwargs[trace_rv_type_str]
pylg_check_bool(self.trace_rv_type, "trace_rv_type")
except (KeyError, ImportError):
self.trace_rv_type = DEFAULT_TRACE_RV_TYPE
self._trace_args = kwopts[trace_args_str]
self._trace_rv = kwopts[trace_rv_str]
self._trace_rv_type = kwopts[trace_rv_type_str]
self.function = None
def __call__(self, *args, **kwargs):
"""Wrapper that is called when a call to a decorated function is made.
""" The actual wrapper that is called when a call to a
decorated function is made. It also handles extra
initialisation when parameters are passed to
It also handles extra initialisation when parameters are passed to
TraceFunction.
:return: The return value of the decorated function.
"""
# ---------------------------------------------------------------------
# __call__ has to behave differently depending on whether the
# decorator has been given any parameters. The reason for this
# is as follows:
# __call__ has to behave differently depending on whether the decorator
# has been given any parameters. The reason for this is as follows:
#
# @TraceFunction
# decorated_function
@ -254,25 +264,24 @@ class TraceFunction(object):
#
# translates to TraceFunction(*args, **kwargs)(decorated_function)
#
# In both cases, the result should be a callable object which
# will be called whenever the decorated function is called. In
# the first case, the callable object is an instance of
# TraceFunction, in the latter case the return value of
# TraceFunction.__call__ is the callable object.
# In both cases, the result should be a callable object which will be
# called whenever the decorated function is called. In the first case,
# the callable object is an instance of TraceFunction, in the latter
# case the return value of TraceFunction.__call__ is the callable
# object.
# ---------------------------------------------------------------------
if self.function is None:
# -----------------------------------------------------------------
# If the decorator has been passed a parameter, __init__
# will not define self.function and __call__ will be
# called immediately after __init__ with the decorated
# function as the only parameter. Therefore, this __call__
# function has to return the callable object that is meant
# to be called every time the decorated function is
# called. Here, self is returned in order to return the
# object as the callable handle for the decorated
# function. This if block should be hit only once at most
# and only during initialisation.
# If the decorator has been passed a parameter, __init__ will not
# define self.function and __call__ will be called immediately
# after __init__ with the decorated function as the only parameter.
# Therefore, this __call__ function has to return the callable
# object that is meant to be called every time the decorated
# function is called. Here, self is returned in order to return the
# object as the callable handle for the decorated function. This if
# block should be hit only once at most and only during
# initialisation.
# -----------------------------------------------------------------
self.init_function(*args, **kwargs)
return self
@ -285,11 +294,13 @@ class TraceFunction(object):
try:
rv = self.function.function(*args, **kwargs)
except Exception as e:
self.trace_exception(e)
except Exception as exc:
self.trace_exception(exc)
if self.exception_exit:
if self._exception_exit:
warnings.warn("Exit forced by EXCEPTION_EXIT")
# pylint: disable=protected-access
os._exit(1)
raise
@ -300,17 +311,13 @@ class TraceFunction(object):
return rv
def init_function(self, *args, **kwargs):
""" Function to initialise the TraceFunctionStruct kept by the
decorator.
"""
"""Initialise the TraceFunctionStruct kept by the decorator."""
# ---------------------------------------------------------------------
# This function should only ever be called with one parameter
# - the function to be decorated. These checks are done here,
# rather than by the caller, as anything that calls this
# function should also have been called with the decorated
# function as its only parameter.
# This function should only ever be called with one parameter - the
# function to be decorated. These checks are done here, rather than by
# the caller, as anything that calls this function should also have
# been called with the decorated function as its only parameter.
# ---------------------------------------------------------------------
assert not kwargs
assert len(args) == 1
@ -329,8 +336,8 @@ class TraceFunction(object):
argspec.defaults))
# ---------------------------------------------------------------------
# init_function is called from either __init__ or __call__ and
# we want the frame before that.
# init_function is called from either __init__ or __call__ and we want
# the frame before that.
# ---------------------------------------------------------------------
frames_back = 2
caller_frame = inspect.stack()[frames_back]
@ -341,10 +348,11 @@ class TraceFunction(object):
self.function.functionname = self.function.function.__name__
def trace_entry(self, *args, **kwargs):
"""Handle function entry.
This function collects all the function arguments and constructs a
message to pass to trace.
""" Called on function entry. This function collects all the
function arguments and constructs a message to pass to
trace.
"""
# ---------------------------------------------------------------------
@ -355,23 +363,22 @@ class TraceFunction(object):
msg += ": "
n_args = len(args)
if self.trace_args:
if self._trace_args:
for arg in range(n_args):
if not TRACE_SELF and \
if not PyLg.trace_self and \
self.function.varnames[arg] == "self":
continue
msg += (self.function.varnames[arg] + " = " +
self.get_value_string(args[arg]) + ", ")
msg += "{} = {}, ".format(
self.function.varnames[arg],
self.get_value_string(args[arg])
)
for name in self.function.varnames[n_args:]:
msg += name + " = "
if name in kwargs:
value = kwargs[name]
else:
value = self.function.defaults[name]
msg += self.get_value_string(value) + ", "
msg += "{} = {}, ".format(
name, kwargs.get(name, self.function.defaults[name])
)
msg = msg[:-2]
@ -381,11 +388,10 @@ class TraceFunction(object):
trace(msg, function=self.function)
def trace_exit(self, rv=None):
"""Handle function exit.
""" Called on function exit to log the fact that a function has
finished executing.
Log the fact that a function has finished executing.
:param rv: The return value of the traced function.
"""
# ---------------------------------------------------------------------
@ -394,22 +400,19 @@ class TraceFunction(object):
msg = "<- EXIT "
if rv is not None:
msg += ": "
if self.trace_rv:
if self._trace_rv:
msg += self.get_value_string(rv)
else:
msg += "---"
if self.trace_rv_type:
msg += " (type: " + type(rv).__name__ + ")"
if self._trace_rv_type:
msg += " (type: {})".format(type(rv).__name__)
trace(msg, function=self.function)
return
def trace_exception(self, exception):
"""Called when a function terminated due to an exception.
""" Called when a function terminated due to an exception.
:param exception: The raised exception.
"""
# ---------------------------------------------------------------------
@ -418,58 +421,55 @@ class TraceFunction(object):
core_msg = type(exception).__name__ + " RAISED"
msg = "<- EXIT : " + core_msg
if str(exception) is not "":
if str(exception) != "":
msg += " - " + str(exception)
if self.exception_warning:
if self._exception_warning:
warnings.warn(core_msg, RuntimeWarning)
if self.exception_tb_file:
if self._exception_tb_file:
msg += "\n--- EXCEPTION ---\n"
msg += traceback.format_exc()
msg += "-----------------"
if self.exception_tb_stderr:
if self._exception_tb_stderr:
print("--- EXCEPTION ---", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
print("-----------------", file=sys.stderr)
trace(msg, function=self.function)
return
def get_value_string(self, value):
@staticmethod
def get_value_string(value):
"""Convert value to a string for the log."""
""" Convert value to a string for the log.
"""
if isinstance(value, list) and PyLg.collapse_lists:
return "[ len={} ]".format(len(value))
if isinstance(value, list) and COLLAPSE_LISTS:
return self.collapse_list(value)
elif isinstance(value, dict) and COLLAPSE_DICTS:
return self.collapse_dict(value)
else:
return str(value)
if isinstance(value, dict) and PyLg.collapse_dicts:
return "{{ len={} }}".format(len(value))
def collapse_list(self, ll):
return "[ len=" + str(len(ll)) + " ]"
def collapse_dict(self, dd):
return "{ len=" + str(len(dd)) + " }"
return "{}".format(value)
def trace(message, function=None):
"""Write message to the log file.
""" Writes message to the log file. It will also log the time,
filename, line number and function name.
It will also log the time, filename, line number and function name.
Parameters
----------
message : str
The log message.
function : Optional[TraceFunctionStruct]
A TraceFunctionStruct object if called from within TraceFunction.
:param str message: The log message.
:param function: A TraceFunctionStruct object if called from within
TraceFunction.
"""
if function is None:
# ---------------------------------------------------------------------
# If there is no function object, we need to work out
# where the trace call was made from.
# If there is no function object, we need to work out where the trace
# call was made from.
# ---------------------------------------------------------------------
frames_back = 1
caller_frame = inspect.stack()[frames_back]
@ -484,37 +484,40 @@ def trace(message, function=None):
functionname = function.functionname
# -------------------------------------------------------------------------
# If CLASS_NAME_RESOLUTION is enabled, the top element of the
# stack should be the class name of the function from which this
# trace call is made. This cannot be policed so the user must make
# sure this is the case by ensuring that trace is only called
# outside of any function or from within functions that have the
# @TraceFunction decorator.
# If _CLASS_NAME_RESOLUTION is enabled, the top element of the stack should
# be the class name of the function from which this trace call is made.
# This cannot be policed so the user must make sure this is the case by
# ensuring that trace is only called outside of any function or from within
# functions that have the @TraceFunction decorator.
# -------------------------------------------------------------------------
classname = ClassNameStack.get()
if classname is not None and classname != "<module>":
functionname = classname + "." + functionname
functionname = "{}.{}".format(classname, functionname)
# -------------------------------------------------------------------------
# Generate the string based on the settings.
# -------------------------------------------------------------------------
msg = ""
if TRACE_TIME:
msg += datetime.now().strftime(TIME_FORMAT) + " "
if PyLg.trace_time:
msg += datetime.now().strftime(PyLg.time_format) + " "
if TRACE_FILENAME:
msg += '{filename:{w}.{w}} '.format(filename=filename,
w=FILENAME_COLUMN_WIDTH)
if PyLg.trace_filename:
msg += "{filename:{w}.{w}} ".format(
filename=filename,
w=PyLg.filename_column_width
)
if TRACE_LINENO:
msg += '{lineno:0{w}}: '.format(lineno=lineno, w=LINENO_WIDTH)
if PyLg.trace_lineno:
msg += "{lineno:0{w}}: ".format(lineno=lineno, w=PyLg.lineno_width)
if TRACE_FUNCTION:
msg += '{function:{w}.{w}} '.format(function=functionname,
w=FUNCTION_COLUMN_WIDTH)
if PyLg.trace_function:
msg += "{function:{w}.{w}} ".format(
function=functionname,
w=PyLg.function_column_width
)
if TRACE_MESSAGE:
if PyLg.trace_message:
message = str(message)
@ -535,20 +538,20 @@ def trace(message, function=None):
# -----------------------------------------------------------------
# Wrap the text.
# -----------------------------------------------------------------
wrapped = textwrap.wrap(line, MESSAGE_WIDTH)
wrapped = textwrap.wrap(line, PyLg.message_width)
if not wrapped:
wrapped = [""]
# -----------------------------------------------------------------
# If this is the first line of the whole trace message, it
# gets special treatment as it doesn't need whitespace in
# front of it. Otherwise, align it with the previous line.
# If this is the first line of the whole trace message, it gets
# special treatment as it doesn't need whitespace in front of it.
# Otherwise, align it with the previous line.
# -----------------------------------------------------------------
if idx != 0:
msg += '{:{w}}'.format('', w=premsglen)
msg += "{:{w}}".format("", w=premsglen)
if MESSAGE_WRAP:
if PyLg.message_wrap:
# -------------------------------------------------------------
# The first wrapped line gets special treatment as any
@ -557,34 +560,38 @@ def trace(message, function=None):
msg += wrapped[0]
# -------------------------------------------------------------
# Print the remaining lines. Append whitespace to
# align it with the first line.
# Print the remaining lines. Append whitespace to align it with
# the first line.
# -------------------------------------------------------------
for wrline in wrapped[1:]:
msg += '\n' + '{:{w}}'.format('', w=premsglen) + wrline
msg += "\n{:{w}}".format('', w=premsglen) + wrline
else:
# -------------------------------------------------------------
# The message is not being wrapped.
# -------------------------------------------------------------
if MESSAGE_MARK_TRUNCATION and wrapped[1:]:
if PyLg.message_mark_truncation and wrapped[1:]:
# ---------------------------------------------------------
# We want to mark truncated lines so we need to
# determine if the line is being truncated. If it
# is we replace the last character with '\'.
# We want to mark truncated lines so we need to determine
# if the line is being truncated. If it is we replace the
# last character with '\'.
# ---------------------------------------------------------
if MESSAGE_WIDTH > 1:
wrapped = textwrap.wrap(wrapped[0], MESSAGE_WIDTH - 1)
if PyLg.message_width > 1:
wrapped = textwrap.wrap(
wrapped[0],
PyLg.message_width - 1
)
assert wrapped
msg += ('{m:{w}}'.format(m=wrapped[0],
w=MESSAGE_WIDTH - 1) +
'\\')
msg += "{m:{w}}\\".format(
m=wrapped[0],
w=PyLg.message_width - 1,
)
else:
assert MESSAGE_WIDTH == 1
assert PyLg.message_width == 1
msg += '\\'
else:

View File

@ -1,167 +1,162 @@
# -----------------------------------------------------------------------------
# PyLg: module to facilitate and automate the process of writing runtime logs.
# Copyright (C) 2017 Wojciech Kozlowski <wk@wojciechkozlowski.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
"""Handlers for PyLg settings."""
from typing import Optional
import os
import yaml
_USER_FILE_NAME = "pylg_settings.yml"
_PYLG_FILE_NAME = "settings.yml"
_BOOL_OPTIONS = set([
"pylg_enable",
"default_exception_warning",
"default_exception_tb_file",
"default_exception_tb_stderr",
"default_exception_exit",
"trace_time",
"trace_filename",
"trace_lineno",
"trace_function",
"class_name_resolution",
"trace_message",
"message_wrap",
"message_mark_truncation",
"trace_self",
"collapse_lists",
"collapse_dicts",
"default_trace_args",
"default_trace_rv",
"default_trace_rv_type",
])
_STRING_OPTIONS = set([
"pylg_file",
"time_format",
])
_POS_INT_OPTIONS = set([
"filename_column_width",
"function_column_width",
])
_NONNEG_OPTIONS = set([
"lineno_width",
"message_width",
])
def load(user_settings_path: Optional[str] = None) -> dict:
"""Load PyLg settings from file.
If a user provides a settings file, it will be used. Otherwise, PyLg's
default settings will be read in.
"""
# -------------------------------------------------------------------------
# Load the default settings.
# -------------------------------------------------------------------------
default_settings_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)), _PYLG_FILE_NAME
)
with open(default_settings_path, 'r') as settings_file:
default_settings = yaml.full_load(settings_file)
# -------------------------------------------------------------------------
# Load the user settings.
# -------------------------------------------------------------------------
user_settings = {}
if user_settings_path is not None:
with open(user_settings_path, 'r') as settings_file:
user_settings = yaml.full_load(settings_file)
# -------------------------------------------------------------------------
# Merge the two settings preferring user values over defaults.
# -------------------------------------------------------------------------
settings = {**default_settings, **user_settings}
# -------------------------------------------------------------------------
# Verify the input.
# -------------------------------------------------------------------------
for option in settings.keys():
source_file = (user_settings_path
if option in user_settings
else default_settings_path)
option_type = type(settings[option]).__name__
option_value = settings[option]
if option in _BOOL_OPTIONS:
if not _pylg_check_bool(option_value):
raise ImportError(
"Invalid type for {} in {} - should be bool, is type {}"
.format(option, source_file, option_type)
)
elif option in _STRING_OPTIONS:
if not _pylg_check_string(option_value):
raise ImportError(
"Invalid type for {} in {} - should be str, is type {}"
.format(option, source_file, option_type)
)
elif option in _POS_INT_OPTIONS:
if not _pylg_check_pos_int(option_value):
raise ImportError(
"Invalid type/value for {} in {} - "
"should be positive int, is {}"
.format(option, source_file, option_value)
)
elif option in _NONNEG_OPTIONS:
if not _pylg_check_nonneg_int(option_value):
raise ImportError(
"Invalid type/value for {} in {} - "
"should be non-negative int, is {}"
.format(option, source_file, option_value)
)
else:
raise ImportError("Unrecognised option in {}: {}"
.format(source_file, option))
# -------------------------------------------------------------------------
# Some final value processing.
# -------------------------------------------------------------------------
if settings["message_width"] == 0:
settings["message_width"] = float("inf")
return settings
# -----------------------------------------------------------------------------
# Enable/disable PyLg.
# Utility functions for sanity checking user settings.
# -----------------------------------------------------------------------------
PYLG_ENABLE = True
def _pylg_check_bool(value) -> bool:
return isinstance(value, bool)
# -----------------------------------------------------------------------------
# The log file name.
# -----------------------------------------------------------------------------
PYLG_FILE = 'pylg.log'
# -----------------------------------------------------------------------------
# The default value for 'exception_warning'. If True, PyLg will print
# a warning about every exception caught to stderr.
# -----------------------------------------------------------------------------
DEFAULT_EXCEPTION_WARNING = True
def _pylg_check_string(value) -> bool:
return isinstance(value, str)
# -----------------------------------------------------------------------------
# The default value for 'exception_tb_file'. If True, PyLg will write
# the exception tracebacks to the log file.
# -----------------------------------------------------------------------------
DEFAULT_EXCEPTION_TB_FILE = True
# -----------------------------------------------------------------------------
# The default value for 'exception_tb_file'. If True, PyLg will print
# the exception tracebacks to stderr.
# -----------------------------------------------------------------------------
DEFAULT_EXCEPTION_TB_STDERR = False
def _pylg_check_int(value) -> bool:
# -----------------------------------------------------------------------------
# The default value for 'exception_exit'. If True, PyLg will force the
# program to exit (and not just raise SystemExit) whenever an
# exception occurs. This will happen even if the exception would be
# handled at a later point.
# -----------------------------------------------------------------------------
DEFAULT_EXCEPTION_EXIT = False
# -------------------------------------------------------------------------
# We check for bool as well as bools are an instance of int, but we don't
# want to let that go through.
# -------------------------------------------------------------------------
return isinstance(value, int) and not isinstance(value, bool)
# -----------------------------------------------------------------------------
# Enable/disable time logging.
# -----------------------------------------------------------------------------
TRACE_TIME = True
# -----------------------------------------------------------------------------
# Formatting for the time trace. For a full list of options, see
# https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior.
# -----------------------------------------------------------------------------
TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
def _pylg_check_nonneg_int(value):
return _pylg_check_int(value) and value >= 0
# -----------------------------------------------------------------------------
# Enable/disable file name logging.
# -----------------------------------------------------------------------------
TRACE_FILENAME = True
# -----------------------------------------------------------------------------
# The column width for the file name. If a name is too long, it will
# be truncated.
# -----------------------------------------------------------------------------
FILENAME_COLUMN_WIDTH = 20
# -----------------------------------------------------------------------------
# Enable/disable the logging of the line number from which the trace
# call was made. For entry and exit messages this logs the line in
# which the decorator is placed (which should be directly above the
# function itself).
# -----------------------------------------------------------------------------
TRACE_LINENO = True
# -----------------------------------------------------------------------------
# The minimum number of digits to use to print the line number. If the
# number is too long, more digits will be used.
# -----------------------------------------------------------------------------
LINENO_WIDTH = 4
# -----------------------------------------------------------------------------
# Enable/disable the logging of the function name from which the trace
# call was made. Entry/exit logs refer to the function they enter into
# and exit from.
# -----------------------------------------------------------------------------
TRACE_FUNCTION = True
# -----------------------------------------------------------------------------
# The column width for the function name. If a name is too long, it
# will be truncated.
# -----------------------------------------------------------------------------
FUNCTION_COLUMN_WIDTH = 32
# -----------------------------------------------------------------------------
# Enable/disable class name resolution. Function names will be printed
# with their class names.
#
# IMPORTANT: If this setting is enabled, the trace function should
# ONLY be called from within functions that have the @TraceFunction
# decorator OR outside of any function.
# -----------------------------------------------------------------------------
CLASS_NAME_RESOLUTION = False
# -----------------------------------------------------------------------------
# Enable/disable message logging.
# -----------------------------------------------------------------------------
TRACE_MESSAGE = True
# -----------------------------------------------------------------------------
# The column width for the message. A width of zero means unlimited.
# -----------------------------------------------------------------------------
MESSAGE_WIDTH = 0
# -----------------------------------------------------------------------------
# If True, PyLg will wrap the message to fit within the column
# width. Otherwise, the message will be truncated.
# -----------------------------------------------------------------------------
MESSAGE_WRAP = True
# -----------------------------------------------------------------------------
# If true, truncated message lines should have the last character
# replaced with '\'. Note that this reduces MESSAGE_WIDTH by 1 for
# truncated lines which may truncate words that would've otherwise
# appeared in the message.
# -----------------------------------------------------------------------------
MESSAGE_MARK_TRUNCATION = True
# -----------------------------------------------------------------------------
# Enable/disable logging of the 'self' function argument.
# -----------------------------------------------------------------------------
TRACE_SELF = False
# -----------------------------------------------------------------------------
# If True lists/dictionaries will be collapsed to '[ len=x ]' and '{
# len=x }' respectively, where x denotes the number of elements in the
# collection.
# -----------------------------------------------------------------------------
COLLAPSE_LISTS = False
COLLAPSE_DICTS = False
# -----------------------------------------------------------------------------
# The default setting for 'trace_args'. If True, PyLg will log input
# parameters.
# -----------------------------------------------------------------------------
DEFAULT_TRACE_ARGS = True
# -----------------------------------------------------------------------------
# The default setting for 'trace_rv'. If True, PyLg will log return
# values.
# -----------------------------------------------------------------------------
DEFAULT_TRACE_RV = True
# -----------------------------------------------------------------------------
# The default settings for 'trace_rv_type'. If True, PyLg will log
# return value types.
# -----------------------------------------------------------------------------
DEFAULT_TRACE_RV_TYPE = False
def _pylg_check_pos_int(value):
return _pylg_check_int(value) and value > 0

143
pylg/settings.yml Normal file
View File

@ -0,0 +1,143 @@
# -----------------------------------------------------------------------------
# Enable/disable PyLg.
# -----------------------------------------------------------------------------
pylg_enable: True
# -----------------------------------------------------------------------------
# The log file name.
# -----------------------------------------------------------------------------
pylg_file: 'pylg.log'
# -----------------------------------------------------------------------------
# The default value for 'exception_warning'. If True, PyLg will print a warning
# about every exception caught to stderr.
# -----------------------------------------------------------------------------
default_exception_warning: True
# -----------------------------------------------------------------------------
# The default value for 'exception_tb_file'. If True, PyLg will write the
# exception tracebacks to the log file.
# -----------------------------------------------------------------------------
default_exception_tb_file: True
# -----------------------------------------------------------------------------
# The default value for 'exception_tb_file'. If True, PyLg will print the
# exception tracebacks to stderr.
# -----------------------------------------------------------------------------
default_exception_tb_stderr: False
# -----------------------------------------------------------------------------
# The default value for 'exception_exit'. If True, PyLg will force the program
# to exit (and not just raise SystemExit) whenever an exception occurs. This
# will happen even if the exception would be handled at a later point.
# -----------------------------------------------------------------------------
default_exception_exit: False
# -----------------------------------------------------------------------------
# Enable/disable time logging.
# -----------------------------------------------------------------------------
trace_time: True
# -----------------------------------------------------------------------------
# Formatting for the time trace. For a full list of options, see
# https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior.
# -----------------------------------------------------------------------------
time_format: "%Y-%m-%d %H:%M:%S.%f"
# -----------------------------------------------------------------------------
# Enable/disable file name logging.
# -----------------------------------------------------------------------------
trace_filename: True
# -----------------------------------------------------------------------------
# The column width for the file name. If a name is too long, it will be
# truncated.
# -----------------------------------------------------------------------------
filename_column_width: 20
# -----------------------------------------------------------------------------
# Enable/disable the logging of the line number from which the trace call was
# made. For entry and exit messages this logs the line in which the decorator
# is placed (which should be directly above the function itself).
# -----------------------------------------------------------------------------
trace_lineno: True
# -----------------------------------------------------------------------------
# The minimum number of digits to use to print the line number. If the number
# is too long, more digits will be used.
# -----------------------------------------------------------------------------
lineno_width: 4
# -----------------------------------------------------------------------------
# Enable/disable the logging of the function name from which the trace call was
# made. Entry/exit logs refer to the function they enter into and exit from.
# -----------------------------------------------------------------------------
trace_function: True
# -----------------------------------------------------------------------------
# The column width for the function name. If a name is too long, it will be
# truncated.
# -----------------------------------------------------------------------------
function_column_width: 32
# -----------------------------------------------------------------------------
# Enable/disable class name resolution. Function names will be printed with
# their class names.
#
# IMPORTANT: If this setting is enabled, the trace function should ONLY be
# called from within functions that have the @TraceFunction decorator OR
# outside of any function.
# -----------------------------------------------------------------------------
class_name_resolution: False
# -----------------------------------------------------------------------------
# Enable/disable message logging.
# -----------------------------------------------------------------------------
trace_message: True
# -----------------------------------------------------------------------------
# The column width for the message. A width of zero means unlimited.
# -----------------------------------------------------------------------------
message_width: 0
# -----------------------------------------------------------------------------
# If True, PyLg will wrap the message to fit within the column width.
# Otherwise, the message will be truncated.
# -----------------------------------------------------------------------------
message_wrap: True
# -----------------------------------------------------------------------------
# If true, truncated message lines should have the last character replaced with
# '\'. Note that this reduces MESSAGE_WIDTH by 1 for truncated lines which may
# truncate words that would've otherwise appeared in the message.
# -----------------------------------------------------------------------------
message_mark_truncation: True
# -----------------------------------------------------------------------------
# Enable/disable logging of the 'self' function argument.
# -----------------------------------------------------------------------------
trace_self: False
# -----------------------------------------------------------------------------
# If True lists/dictionaries will be collapsed to '[ len=x ]' and '{ len=x }'
# respectively, where x denotes the number of elements in the collection.
# -----------------------------------------------------------------------------
collapse_lists: False
collapse_dicts: False
# -----------------------------------------------------------------------------
# The default setting for 'trace_args'. If True, PyLg will log input
# parameters.
# -----------------------------------------------------------------------------
default_trace_args: True
# -----------------------------------------------------------------------------
# The default setting for 'trace_rv'. If True, PyLg will log return values.
# -----------------------------------------------------------------------------
default_trace_rv: True
# -----------------------------------------------------------------------------
# The default settings for 'trace_rv_type'. If True, PyLg will log return value
# types.
# -----------------------------------------------------------------------------
default_trace_rv_type: False

63
pylg/settings_to_yml.py Normal file
View File

@ -0,0 +1,63 @@
"""Convert old-style settings.py to new-style settings.yml."""
import warnings
import sys
from os import path
def convert(source, destination):
"""Convert source python settings file to destination yml file.
Parameters
----------
source : str
File name of the old-style Python settings file.
destination: str
File name for the new-style YAML settings file.
"""
with open(source, 'r') as src, open(destination, 'w') as dst:
for line in src:
if not line.lstrip().startswith('#') and '=' in line:
key, val = line.split('=')
key = key.strip().lower()
val = val.strip()
dst.write("{}: {}\n".format(key, val))
else:
dst.write(line)
def settings_to_yml():
"""Convert user settings file from old-style python to new-style YAML."""
settings_py = "pylg_settings.py"
root_dir = path.dirname(sys.modules['__main__'].__file__)
settings_py_path = path.join(root_dir, settings_py)
if path.isfile(settings_py_path):
warnings.warn(
"Deprecated {} found".format(settings_py),
DeprecationWarning,
)
settings_yml = "{}.yml".format(settings_py[:-3])
settings_yml_path = path.join(root_dir, settings_yml)
if path.isfile(settings_yml_path):
warnings.warn(
"Could not convert {py} to {yml}, {yml} already exists. "
"If this is the converted settings file, delete {py} to get "
"rid of this warning."
.format(py=settings_py, yml=settings_yml),
DeprecationWarning,
)
return
warnings.warn(
"Converting {} to {}".format(settings_py, settings_yml),
DeprecationWarning,
)
convert(settings_py_path, settings_yml_path)
if __name__ == "__main__": # pragma: no cover
settings_to_yml()

0
pylg/tests/__init__.py Normal file
View File

5
pylg/tests/test_pylg.py Normal file
View File

@ -0,0 +1,5 @@
from pylg import TraceFunction, trace
import sys
def test_load():
print(sys.modules['__main__'].__file__)

571
pylintrc Normal file
View File

@ -0,0 +1,571 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Specify a configuration file.
#rcfile=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=print-statement,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
rv,
_
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
[STRING]
# This flag controls whether the implicit-str-concat-in-sequence should
# generate a warning on implicit string concatenation in sequences defined over
# several lines.
check-str-concat-over-line-jumps=no
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: en_GB (myspell), en_US
# (myspell)..
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[LOGGING]
# Format style used to check logging format string. `old` means using %
# formatting, while `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[DESIGN]
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement.
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[IMPORTS]
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception

View File

@ -1,26 +1,44 @@
from setuptools import setup, find_packages
from codecs import open
"""PyLg setup file."""
from codecs import open as codecs_open
from os import path
pwd = path.abspath(path.dirname(__file__))
from setuptools import setup
with open(path.join(pwd, 'README.rst'), encoding='utf-8') as f:
long_description = f.read()
PWD = path.abspath(path.dirname(__file__))
with codecs_open(path.join(PWD, 'README.rst'), encoding='utf-8') as f:
LONG_DESCRIPTION = f.read()
INSTALL_REQUIRES = [
'pyyaml',
]
EXTRAS_REQUIRE = {
'dev': [
'pytest',
'pytest-cov',
]
}
setup(
name='PyLg',
version='1.3.3',
description='Python module to facilitate and automate the process of writing runtime logs.',
long_description=long_description,
description=('Python module to facilitate and automate the process of '
'writing runtime logs.'),
long_description=LONG_DESCRIPTION,
url='https://github.com/Wojtek242/pylg',
author='Wojciech Kozlowski',
author_email='wk@wojciechkozlowski.eu',
install_requires=INSTALL_REQUIRES,
extras_require=EXTRAS_REQUIRE,
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Topic :: Software Development :: Debuggers',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
('License :: OSI Approved :: '
'GNU General Public License v3 or later (GPLv3+)'),
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
],