diff --git a/clog/_logger.py b/clog/_logger.py index d2224ac..ada75de 100644 --- a/clog/_logger.py +++ b/clog/_logger.py @@ -1,14 +1,14 @@ +from __future__ import annotations import os import sys from io import TextIOWrapper -from typing import TextIO, Union +from typing import IO, Any, Type, Union from collections import namedtuple from .utils import common from .utils import printfmt -class Logger: ... # class delcaration class Logger: # class redeclaration & initialisation """A simple logging class to write messages directly to the console or to a log file. @@ -47,21 +47,21 @@ class Logger: # class redeclaration & initialisation not writing to console on STDOUT or STDERR. """ ## set the PIPE to STDOUT by default - __stdpipe: TextIO = sys.stdout + __stdpipe: IO[Any] = sys.stdout ## detect if there's a redirect __IS_STDOUT_REDIR: bool = os.isatty(sys.stdout.fileno()) __IS_STDERR_REDIR: bool = os.isatty(sys.stderr.fileno()) ## the default log out file __DEFAULT_OUT_FILE: str = "dump.log" ## log info namedtuple for storing class states - __LOG_INFO_TUPLE = namedtuple('LogInfo', + __LOG_INFO_TUPLE = namedtuple('__LOG_INFO_TUPLE', ['isatty', 'lv', 'msg', 'sep', 'end']) ## create instance attribute for class singleton __instance__ = None ## create instance attribute as read-only for log location log = None - def __new__(cls, *, out_f: str = ...) -> Logger: + def __new__(cls, *, out_f: Union[str, None] = None) -> Logger: """Construct a new instance of the class and initialise it. Constructor method is used to establish the class as a @@ -73,7 +73,7 @@ class Logger: # class redeclaration & initialisation cls.__instance__ = super(Logger, cls).__new__(cls) # establish singleton instance cls.log = cls.__DEFAULT_OUT_FILE # initialise attribute to default ## handle if a custom file pathspec was given - if out_f is not Ellipsis and isinstance(out_f, str): + if out_f is not None and isinstance(out_f, str): ## verify path and convert to real pathspec. if common.is_path_spec(out_f): ## redefine the default log out attribute and create @@ -81,7 +81,9 @@ class Logger: # class redeclaration & initialisation cls.log = cls.__DEFAULT_OUT_FILE = \ os.path.realpath(out_f).strip('"') - cls.__loginfo = None # default the namedtuple to None on first instance + cls.__loginfo: Logger.__LOG_INFO_TUPLE = Logger.__LOG_INFO_TUPLE( + *([None] * 5) + ) # default the namedtuple to None on first instance cls.printLog2File("----[New instance of script has been started]----", file=cls.__DEFAULT_OUT_FILE, mode='w') @@ -91,7 +93,7 @@ class Logger: # class redeclaration & initialisation @classmethod def debug(cls, *value: object, sep: Union[str, None] = None, end: Union[str, None] = None, wrapping: bool = True, - strace: bool = True) -> Logger: + strace: bool = True) -> Type[Logger]: """Pseudolog for writing to log file with level `LogLevel.DEBUG`. Method is invoked on a `Logger` instance and will directly write @@ -106,7 +108,7 @@ class Logger: # class redeclaration & initialisation wrapping=wrapping, strace=strace) Logger.__stdpipe = sys.stderr - cls.__loginfo: namedtuple = Logger.__LOG_INFO_TUPLE( + cls.__loginfo = Logger.__LOG_INFO_TUPLE( Logger.__IS_STDERR_REDIR, common.LogLevel.DEBUG, value, sep, end ) @@ -115,7 +117,7 @@ class Logger: # class redeclaration & initialisation @classmethod def warn(cls, *value: object, sep: Union[str, None] = None, end: Union[str, None] = None, wrapping: bool = True, - strace: bool = True) -> Logger: + strace: bool = True) -> Type[Logger]: """Pseudolog for writing to log file with level `LogLevel.WARN`. Method is invoked on a `Logger` instance and will directly write @@ -130,7 +132,7 @@ class Logger: # class redeclaration & initialisation end=end, wrapping=wrapping, strace=strace) Logger.__stdpipe = sys.stderr - cls.__loginfo: namedtuple = Logger.__LOG_INFO_TUPLE( + cls.__loginfo = Logger.__LOG_INFO_TUPLE( Logger.__IS_STDERR_REDIR, common.LogLevel.WARN, value, sep, end ) @@ -139,7 +141,7 @@ class Logger: # class redeclaration & initialisation @classmethod def error(cls, *value: object, sep: Union[str, None] = None, end: Union[str, None] = None, wrapping: bool = True, - strace: bool = True) -> Logger: + strace: bool = True) -> Type[Logger]: """Pseudolog for writing to log file with level `LogLevel.ERROR`. Method is invoked on a `Logger` instance and will directly write @@ -154,7 +156,7 @@ class Logger: # class redeclaration & initialisation end=end, wrapping=wrapping, strace=strace) Logger.__stdpipe = sys.stderr - cls.__loginfo: namedtuple = Logger.__LOG_INFO_TUPLE( + cls.__loginfo = Logger.__LOG_INFO_TUPLE( Logger.__IS_STDERR_REDIR, common.LogLevel.ERROR, value, sep, end ) @@ -183,7 +185,7 @@ class Logger: # class redeclaration & initialisation @staticmethod def printLog2File(*value: object, level: Union[common.LogLevel, int] = common.LogLevel.DEBUG, - mode: str = 'a', file: Union[TextIOWrapper, str] = ..., + mode: str = 'a', file: Union[TextIOWrapper, str, None] = None, sep: Union[str, None] = None, end: Union[str, None] = None, wrapping: bool = True, strace: bool = True, header: bool = True) -> None: """Wrapper method over the built-in `print()` function defined @@ -233,7 +235,13 @@ class Logger: # class redeclaration & initialisation the default pathspec used is defined by the `Logger` instance. """ ## handle if no file parameter was given - if file is Ellipsis or not os.path.exists(file): + if file is None or not isinstance(file, (TextIOWrapper, str)): + file = Logger.__DEFAULT_OUT_FILE + + if not isinstance(file, str): + file = file.name + + if not os.path.exists(file): file = Logger.__DEFAULT_OUT_FILE _frame = sys._getframe(2) if sys._getframe(1).f_code.co_name in \ @@ -242,33 +250,29 @@ class Logger: # class redeclaration & initialisation _fname = _frame.f_code.co_filename.removeprefix( os.getcwd()).strip('\\/') ## generate new header for log file and construct new message + _val = [*map(str, value)] # convert all objects to string if header: msg = printfmt.gen_log_header(level).format( - " ".join(value), CALLER="{0}:{1}[{2}]".format( + " ".join(_val), CALLER="{0}:{1}[{2}]".format( _fname, _frame.f_code.co_name, _frame.f_lineno ).replace("module", "global") if strace else "LOGGER" ) else: - msg = " ".join(value) + msg = " ".join(_val) # perform wrapping of message and indent wrapped lines if wrapping: msg = printfmt.wrap(msg).replace('\n', '\n\t') - if isinstance(file, str): - with open(file, mode, encoding="utf-8") as log: - Logger.__stdpipe = log # pre-requisite to write PIPE to file - Logger.__printLog__(False, level, (msg,), sep, end, False) - elif isinstance(file, TextIOWrapper): - Logger.__stdpipe = log + with open(file, mode, encoding="utf-8") as log: + Logger.__stdpipe = log # pre-requisite to write PIPE to file Logger.__printLog__(False, level, (msg,), sep, end, False) @staticmethod def printLog(*value: object, level: Union[int, common.LogLevel] = common.LogLevel.NORMAL, sep: Union[str, None] = None, end: Union[str, None] = None, - file: Union[TextIOWrapper, None] = None, - flush: bool = True) -> None: + file=None, flush: bool = True) -> None: """Wrapper method over the built-in `print()` function defined using 3.x syntax. All Familiar functionality can be passed to the method as found when calling `print()`, but comes with added @@ -312,7 +316,7 @@ class Logger: # class redeclaration & initialisation ## configure PIPE to STDERR if logging is high enough if file is None: - if level >= common.LogLevel.DEBUG: + if level >= common.LogLevel.DEBUG: # type: ignore Logger.__stdpipe = sys.stderr else: Logger.__stdpipe = sys.stdout @@ -330,7 +334,7 @@ class Logger: # class redeclaration & initialisation ### :@NOTE: if there's a PIPE redirect, don't use colour ### for that redirect PIPE - if level < common.LogLevel.WARN: # handle output for STDOUT + if level < common.LogLevel.WARN: # type: ignore # handle output for STDOUT Logger.__printLog__(Logger.__IS_STDOUT_REDIR, level, value, sep, end, flush) else: # handle output for STDERR @@ -338,21 +342,20 @@ class Logger: # class redeclaration & initialisation sep, end, flush) @staticmethod - def __printLog__(isatty: bool, lv: common.LogLevel, msg: object, - s: Union[str, None] = None, e: Union[str, None] = None, - flsh: bool = True) -> None: + def __printLog__(isatty: bool, lv: Union[common.LogLevel, int], + msg: object, s: Union[str, None] = None, + e: Union[str, None] = None, flsh: bool = True) -> None: """Private helper method responsible for invoking the built-in `print` function with appropriate keyword arugments. Method identifies the PIPE used and provide text highlighting accordingly. """ ## handle if we have a redirect - if isatty and (Logger.__stdpipe is sys.stdout - or Logger.__stdpipe is sys.stderr): + if isatty and (Logger.__stdpipe is sys.stdout or Logger.__stdpipe is sys.stderr): ## write ANSI code to start coloured text print(printfmt.log_as_col(lv), end="", file=Logger.__stdpipe, flush=flsh) ## unpack the object and pass to print - print(*msg, sep=s, end="", file=Logger.__stdpipe, flush=flsh) + print(*msg, sep=s, end="", file=Logger.__stdpipe, flush=flsh) # type: ignore ## reset the colour sequence back to normal print(printfmt.Colours.NORMAL, end=e, file=Logger.__stdpipe, flush=flsh) else: - print(*msg, sep=s, end=e, file=Logger.__stdpipe, flush=flsh) \ No newline at end of file + print(*msg, sep=s, end=e, file=Logger.__stdpipe, flush=flsh) # type: ignore \ No newline at end of file diff --git a/clog/utils/common.py b/clog/utils/common.py index 92e6b75..862b842 100644 --- a/clog/utils/common.py +++ b/clog/utils/common.py @@ -49,6 +49,5 @@ def is_path_spec(path_spec: str) -> bool: ## perform a regex match to ensure that the given path is a ## valid pathspec for the system. return bool(re.match( - __REGEX_PAT, path_spec.strip(r"\"'")) - or re.match(r'^[\w\d\-_]+$', path_spec.strip(r"\"'")) + __REGEX_PAT, path_spec.strip(r"\"'")) or re.match(r'^[\w\d\-_]+$', path_spec.strip(r"\"'")) ) \ No newline at end of file diff --git a/clog/utils/printfmt.py b/clog/utils/printfmt.py index 614a70b..d662a87 100644 --- a/clog/utils/printfmt.py +++ b/clog/utils/printfmt.py @@ -38,7 +38,7 @@ def loglevel_as_str(level: Union[LogLevel, int]) -> str: return " " * 5 -def log_as_col(level: Union[int, LogLevel]) -> Colours: +def log_as_col(level: Union[int, LogLevel]) -> str: """Convert an integer or enum value into an associated ANSI escape code terminal colour sequence. Depending on log level severity, an associated colour is returned to give STDOUT text a distinct @@ -95,9 +95,8 @@ def gen_log_header(_type: Union[LogLevel, int, str]) -> str: _type = loglevel_as_str(_type) ## return newly formatted log message header - return __LOG_TMPL.format('{CALLER}', DATE=\ - time.strftime(__TIMESTAMP_FMT, time.localtime()), - TYPE=_type) + "{0}" + return __LOG_TMPL.format('{CALLER}', TYPE=_type, + DATE=time.strftime(__TIMESTAMP_FMT, time.localtime())) + "{0}" def wrap(value: str, *, width: int = 120, tb_size: int = 4) -> str: