Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a00c27c465 | ||
|
|
1f7f4b8d53 | ||
|
|
baca79ce82 | ||
|
|
4265be6430 | ||
|
|
c16b086467 | ||
|
|
48a98c0206 | ||
|
|
f1788f057f | ||
|
|
0ad8883809 | ||
|
|
51e9b1ce7c | ||
|
|
0d3104f60a | ||
|
|
d29f827fc9 |
@@ -1,7 +1,7 @@
|
||||
# MARK: Project info
|
||||
[project]
|
||||
name = "corelibs"
|
||||
version = "0.37.0"
|
||||
version = "0.40.0"
|
||||
description = "Collection of utils for Python scripts"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
|
||||
@@ -93,6 +93,8 @@ class SettingsLoader:
|
||||
entry_split_char: dict[str, str] = {}
|
||||
# entries that should be converted
|
||||
entry_convert: dict[str, str] = {}
|
||||
# no args to set
|
||||
args_none: list[str] = []
|
||||
# all the settings for the config id given
|
||||
settings: dict[str, dict[str, Any]] = {
|
||||
config_id: {},
|
||||
@@ -162,6 +164,8 @@ class SettingsLoader:
|
||||
f"[!] In [{config_id}] the split character setup for entry failed: {check}: {e}",
|
||||
'CRITICAL'
|
||||
)) from e
|
||||
if check == "args:no":
|
||||
args_none.append(key)
|
||||
if skip:
|
||||
continue
|
||||
settings[config_id][key] = [
|
||||
@@ -185,9 +189,15 @@ class SettingsLoader:
|
||||
error: bool = False
|
||||
for entry, validate in config_validate.items():
|
||||
# if we have command line option set, this one overrides config
|
||||
if self.__get_arg(entry):
|
||||
if (args_entry := self.__get_arg(entry)) is not None:
|
||||
self.__print(f"[*] Command line option override for: {entry}", 'WARNING')
|
||||
settings[config_id][entry] = self.args.get(entry)
|
||||
if (
|
||||
entry not in args_none and
|
||||
(isinstance(args_entry, list) and entry_split_char.get(entry)) or
|
||||
(not isinstance(args_entry, list) and not entry_split_char.get(entry))
|
||||
):
|
||||
# args is list, but entry has not split, do not set
|
||||
settings[config_id][entry] = args_entry
|
||||
# validate checks
|
||||
for check in validate:
|
||||
# CHECKS
|
||||
|
||||
@@ -32,6 +32,7 @@ class ConsoleFormat(Flag):
|
||||
FILE = auto()
|
||||
FUNCTION = auto()
|
||||
LINENO = auto()
|
||||
LEVEL = auto()
|
||||
|
||||
|
||||
class ConsoleFormatSettings:
|
||||
@@ -43,14 +44,17 @@ class ConsoleFormatSettings:
|
||||
ConsoleFormat.NAME |
|
||||
ConsoleFormat.FILE |
|
||||
ConsoleFormat.FUNCTION |
|
||||
ConsoleFormat.LINENO
|
||||
ConsoleFormat.LINENO |
|
||||
ConsoleFormat.LEVEL
|
||||
)
|
||||
# show time with no time zone, file and line
|
||||
CONDENSED = ConsoleFormat.TIME | ConsoleFormat.FILE | ConsoleFormat.LINENO
|
||||
# only time
|
||||
MINIMAL = ConsoleFormat.TIME
|
||||
# show time with no time zone, file, line and level
|
||||
CONDENSED = ConsoleFormat.TIME | ConsoleFormat.FILE | ConsoleFormat.LINENO | ConsoleFormat.LEVEL
|
||||
# only time and level
|
||||
MINIMAL = ConsoleFormat.TIME | ConsoleFormat.LEVEL
|
||||
# only level
|
||||
BARE = ConsoleFormat.LEVEL
|
||||
# only message
|
||||
BARE = ConsoleFormat(0)
|
||||
NONE = ConsoleFormat(0)
|
||||
|
||||
@staticmethod
|
||||
def from_string(setting_str: str, default: ConsoleFormat | None = None) -> ConsoleFormat | None:
|
||||
@@ -445,6 +449,9 @@ class Log(LogParent):
|
||||
logger setup
|
||||
"""
|
||||
|
||||
CONSOLE_HANDLER: str = 'stream_handler'
|
||||
FILE_HANDLER: str = 'file_handler'
|
||||
|
||||
# spacer lenght characters and the character
|
||||
SPACER_CHAR: str = '='
|
||||
SPACER_LENGTH: int = 32
|
||||
@@ -510,13 +517,13 @@ class Log(LogParent):
|
||||
# in the file writer too, for the ones where color is set BEFORE the format
|
||||
# Any is logging.StreamHandler, logging.FileHandler and all logging.handlers.*
|
||||
self.handlers: dict[str, Any] = {}
|
||||
self.add_handler('file_handler', self.__create_file_handler(
|
||||
'file_handler', self.log_settings['log_level_file'], log_path)
|
||||
self.add_handler(self.FILE_HANDLER, self.__create_file_handler(
|
||||
self.FILE_HANDLER, self.log_settings['log_level_file'], log_path)
|
||||
)
|
||||
if self.log_settings['console_enabled']:
|
||||
# console
|
||||
self.add_handler('stream_handler', self.__create_console_handler(
|
||||
'stream_handler',
|
||||
self.add_handler(self.CONSOLE_HANDLER, self.__create_console_handler(
|
||||
self.CONSOLE_HANDLER,
|
||||
self.log_settings['log_level_console'],
|
||||
console_format_type=self.log_settings['console_format_type'],
|
||||
))
|
||||
@@ -613,19 +620,17 @@ class Log(LogParent):
|
||||
self.handlers[handler_name] = handler
|
||||
return True
|
||||
|
||||
# MARK: console handler
|
||||
def __create_console_handler(
|
||||
self, handler_name: str,
|
||||
log_level_console: LoggingLevel = LoggingLevel.WARNING,
|
||||
filter_exceptions: bool = True,
|
||||
console_format_type: ConsoleFormat = ConsoleFormatSettings.ALL,
|
||||
) -> logging.StreamHandler[TextIO]:
|
||||
# console logger
|
||||
if not self.validate_log_level(log_level_console):
|
||||
log_level_console = self.DEFAULT_LOG_LEVEL_CONSOLE
|
||||
console_handler = logging.StreamHandler()
|
||||
print(f"Console format type: {console_format_type}")
|
||||
# build the format string based on what flags are set
|
||||
# MARK: console logger format
|
||||
def __build_console_format_from_string(self, console_format_type: ConsoleFormat) -> str:
|
||||
"""
|
||||
Build console format string from the given console format type
|
||||
|
||||
Arguments:
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
format_string = ''
|
||||
# time part if any of the times are requested
|
||||
if (
|
||||
@@ -654,17 +659,23 @@ class Log(LogParent):
|
||||
set_group.append('%(lineno)d')
|
||||
format_string += ':'.join(set_group)
|
||||
format_string += '] '
|
||||
# always level + message
|
||||
format_string += '<%(levelname)s> %(message)s'
|
||||
# basic date, but this will be overridden to ISO in formatTime
|
||||
# format_date = "%Y-%m-%d %H:%M:%S"
|
||||
# color or not
|
||||
if self.log_settings['console_color_output_enabled']:
|
||||
# formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
|
||||
formatter_console = CustomConsoleFormatter(format_string)
|
||||
else:
|
||||
# formatter_console = logging.Formatter(format_string, datefmt=format_date)
|
||||
formatter_console = logging.Formatter(format_string)
|
||||
# level if wanted
|
||||
if ConsoleFormat.LEVEL in console_format_type:
|
||||
format_string += '<%(levelname)s> '
|
||||
# always message
|
||||
format_string += '%(message)s'
|
||||
return format_string
|
||||
|
||||
def __set_time_format_for_console_formatter(
|
||||
self, formatter_console: CustomConsoleFormatter | logging.Formatter, console_format_type: ConsoleFormat
|
||||
) -> None:
|
||||
"""
|
||||
Format time for a given format handler, this is for console format only
|
||||
|
||||
Arguments:
|
||||
formatter_console {CustomConsoleFormatter | logging.Formatter} -- _description_
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
"""
|
||||
# default for TIME is milliseconds
|
||||
# if we have multiple set, the smallest precision wins
|
||||
if ConsoleFormat.TIME_MICROSECONDS in console_format_type:
|
||||
@@ -701,11 +712,80 @@ class Log(LogParent):
|
||||
.fromtimestamp(record.created)
|
||||
.isoformat(sep=" ", timespec=iso_precision)
|
||||
)
|
||||
|
||||
def __set_console_formatter(self, console_format_type: ConsoleFormat) -> CustomConsoleFormatter | logging.Formatter:
|
||||
"""
|
||||
Build the full formatter and return it
|
||||
|
||||
Arguments:
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
|
||||
Returns:
|
||||
CustomConsoleFormatter | logging.Formatter -- _description_
|
||||
"""
|
||||
format_string = self.__build_console_format_from_string(console_format_type)
|
||||
if self.log_settings['console_color_output_enabled']:
|
||||
# formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
|
||||
formatter_console = CustomConsoleFormatter(format_string)
|
||||
else:
|
||||
# formatter_console = logging.Formatter(format_string, datefmt=format_date)
|
||||
formatter_console = logging.Formatter(format_string)
|
||||
self.__set_time_format_for_console_formatter(formatter_console, console_format_type)
|
||||
self.log_settings['console_format_type'] = console_format_type
|
||||
return formatter_console
|
||||
|
||||
# MARK: console handler update
|
||||
def update_console_formatter(
|
||||
self,
|
||||
console_format_type: ConsoleFormat,
|
||||
):
|
||||
"""
|
||||
Update the console formatter for format layout and time stamp format
|
||||
|
||||
Arguments:
|
||||
console_format_type {ConsoleFormat} -- _description_
|
||||
"""
|
||||
# skip if console not enabled
|
||||
if not self.log_settings['console_enabled']:
|
||||
return
|
||||
# skip if format has not changed
|
||||
if self.log_settings['console_format_type'] == console_format_type:
|
||||
return
|
||||
# update the formatter
|
||||
self.handlers[self.CONSOLE_HANDLER].setFormatter(
|
||||
self.__set_console_formatter(console_format_type)
|
||||
)
|
||||
|
||||
# MARK: console handler
|
||||
def __create_console_handler(
|
||||
self, handler_name: str,
|
||||
log_level_console: LoggingLevel = LoggingLevel.WARNING,
|
||||
filter_exceptions: bool = True,
|
||||
console_format_type: ConsoleFormat = ConsoleFormatSettings.ALL,
|
||||
) -> logging.StreamHandler[TextIO]:
|
||||
# console logger
|
||||
if not self.validate_log_level(log_level_console):
|
||||
log_level_console = self.DEFAULT_LOG_LEVEL_CONSOLE
|
||||
console_handler = logging.StreamHandler()
|
||||
# print(f"Console format type: {console_format_type}")
|
||||
# build the format string based on what flags are set
|
||||
# format_string = self.__build_console_format_from_string(console_format_type)
|
||||
# # basic date, but this will be overridden to ISO in formatTime
|
||||
# # format_date = "%Y-%m-%d %H:%M:%S"
|
||||
# # color or not
|
||||
# if self.log_settings['console_color_output_enabled']:
|
||||
# # formatter_console = CustomConsoleFormatter(format_string, datefmt=format_date)
|
||||
# formatter_console = CustomConsoleFormatter(format_string)
|
||||
# else:
|
||||
# # formatter_console = logging.Formatter(format_string, datefmt=format_date)
|
||||
# formatter_console = logging.Formatter(format_string)
|
||||
# # set the time format
|
||||
# self.__set_time_format_for_console_formatter(formatter_console, console_format_type)
|
||||
console_handler.set_name(handler_name)
|
||||
console_handler.setLevel(log_level_console.name)
|
||||
# do not show exceptions logs on console
|
||||
console_handler.addFilter(CustomHandlerFilter('console', filter_exceptions))
|
||||
console_handler.setFormatter(formatter_console)
|
||||
console_handler.setFormatter(self.__set_console_formatter(console_format_type))
|
||||
return console_handler
|
||||
|
||||
# MARK: file handler
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
foo=bar
|
||||
foobar=1
|
||||
bar=st
|
||||
arg_overload=should_not_be_set_because_of_command_line_is_list
|
||||
arg_overload_list=too,be,long
|
||||
arg_overload_not_set=this should not be set because of override flag
|
||||
just_values=too,be,long
|
||||
some_match=foo
|
||||
some_match_list=foo,bar
|
||||
test_list=a,b,c,d f, g h
|
||||
|
||||
@@ -39,7 +39,10 @@ def main():
|
||||
|
||||
sl = SettingsLoader(
|
||||
{
|
||||
'foo': 'OVERLOAD'
|
||||
'foo': 'OVERLOAD',
|
||||
'arg_overload': ['should', 'not', 'be', 'set'],
|
||||
'arg_overload_list': ['overload', 'this', 'list'],
|
||||
'arg_overload_not_set': "DO_NOT_SET",
|
||||
},
|
||||
ROOT_PATH.joinpath(CONFIG_DIR, CONFIG_FILE),
|
||||
log=log
|
||||
@@ -53,6 +56,8 @@ def main():
|
||||
"foo": ["mandatory:yes"],
|
||||
"foobar": ["check:int"],
|
||||
"bar": ["mandatory:yes"],
|
||||
"arg_overload_list": ["split:,",],
|
||||
"arg_overload_not_set": ["args:no"],
|
||||
"some_match": ["matching:foo|bar"],
|
||||
"some_match_list": ["split:,", "matching:foo|bar"],
|
||||
"test_list": [
|
||||
|
||||
@@ -25,12 +25,13 @@ def main():
|
||||
"log_level_file": 'DEBUG',
|
||||
# "console_color_output_enabled": False,
|
||||
"per_run_log": True,
|
||||
# Set console log type, must be sent as value for ConsoleFormat or bitwise of ConsoleFormatType
|
||||
# "console_format_type": ConsoleFormatSettings.BARE,
|
||||
# "console_format_type": ConsoleFormatSettings.NONE,
|
||||
# "console_format_type": ConsoleFormatSettings.MINIMAL,
|
||||
# "console_format_type": ConsoleFormatType.TIME_MICROSECONDS | ConsoleFormatType.NAME,
|
||||
# "console_format_type": ConsoleFormatType.NAME,
|
||||
"console_format_type": ConsoleFormat.TIME | ConsoleFormat.TIMEZONE | ConsoleFormat.LINENO,
|
||||
"console_format_type": ConsoleFormat.TIME_MICROSECONDS | ConsoleFormat.NAME | ConsoleFormat.LEVEL,
|
||||
# "console_format_type": ConsoleFormat.NAME,
|
||||
# "console_format_type": (
|
||||
# ConsoleFormat.TIME | ConsoleFormat.TIMEZONE | ConsoleFormat.LINENO | ConsoleFormat.LEVEL
|
||||
# ),
|
||||
}
|
||||
)
|
||||
logn = Logger(log.get_logger_settings())
|
||||
@@ -104,10 +105,16 @@ def main():
|
||||
|
||||
for key, handler in log.handlers.items():
|
||||
print(f"Handler (handlers) [{key}] {handler} -> {handler.level} -> {LoggingLevel.from_any(handler.level)}")
|
||||
log.set_log_level('stream_handler', LoggingLevel.ERROR)
|
||||
log.set_log_level(Log.CONSOLE_HANDLER, LoggingLevel.ERROR)
|
||||
log.logger.warning('[NORMAL] Invisible Warning test: %s', log.logger.name)
|
||||
log.logger.error('[NORMAL] Visible Error test: %s', log.logger.name)
|
||||
# log.handlers['stream_handler'].se
|
||||
|
||||
log.set_log_level(Log.CONSOLE_HANDLER, LoggingLevel.DEBUG)
|
||||
log.debug('Current logging format: %s', log.log_settings['console_format_type'])
|
||||
log.update_console_formatter(ConsoleFormat.TIME | ConsoleFormat.LINENO)
|
||||
log.info('Does hit show less')
|
||||
log.update_console_formatter(ConsoleFormat.TIME | ConsoleFormat.LINENO)
|
||||
log.info('Does hit show less B')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -528,6 +528,123 @@ class TestLoadSettings:
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_no_flag(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test args:no flag with list argument that has split"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=a,b,c\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["x", "y", "z"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["args:no", "split:,"]}
|
||||
)
|
||||
|
||||
# With args:no and split defined for list args, should use config value
|
||||
assert result["value"] == ["a", "b", "c"]
|
||||
captured = capsys.readouterr()
|
||||
# Message is printed but args:no prevents the override
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_list_no_split(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test that list arguments without split entry are skipped"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config_value\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["arg1", "arg2", "arg3"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": []}
|
||||
)
|
||||
|
||||
# Should keep config value since args is list but no split defined
|
||||
assert result["value"] == "config_value"
|
||||
captured = capsys.readouterr()
|
||||
# Message is printed but list without split prevents the override
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_list_with_split(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test that list arguments with split entry are applied"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=a,b,c\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["arg1", "arg2", "arg3"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["split:,"]}
|
||||
)
|
||||
|
||||
# Should use args value since split is defined
|
||||
assert result["value"] == ["arg1", "arg2", "arg3"]
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_no_with_mandatory(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test args:no with mandatory field and list args with split"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config1,config2\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": ["arg1", "arg2"]},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["mandatory:yes", "args:no", "split:,"]}
|
||||
)
|
||||
|
||||
# Should use config value because of args:no with list args and split
|
||||
assert result["value"] == ["config1", "config2"]
|
||||
captured = capsys.readouterr()
|
||||
# Message is printed but args:no prevents the override
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_no_with_mandatory_valid(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test args:no with string args still allows override (current behavior)"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config_value\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": "arg_value"},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": ["mandatory:yes", "args:no"]}
|
||||
)
|
||||
|
||||
# Current behavior: string args without split still override even with args:no
|
||||
assert result["value"] == "arg_value"
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_args_string_no_split(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test that string arguments without split entry work normally"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text("[TestSection]\nvalue=config_value\n")
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={"value": "arg_value"},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"TestSection",
|
||||
{"value": []}
|
||||
)
|
||||
|
||||
# Should use args value for non-list args
|
||||
assert result["value"] == "arg_value"
|
||||
captured = capsys.readouterr()
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
def test_load_settings_no_config_file_with_args(self, tmp_path: Path):
|
||||
"""Test loading settings without config file but with mandatory args"""
|
||||
config_file = tmp_path / "missing.ini"
|
||||
@@ -704,5 +821,48 @@ class TestComplexScenarios:
|
||||
assert result["emails"] == "test@example.com"
|
||||
assert result["date"] == "2025-01-15"
|
||||
|
||||
def test_args_no_and_list_skip_combination(self, tmp_path: Path, capsys: CaptureFixture[str]):
|
||||
"""Test combination of args:no flag and list argument skip behavior"""
|
||||
config_file = tmp_path / "test.ini"
|
||||
config_file.write_text(
|
||||
"[Settings]\n"
|
||||
"no_override=a,b,c\n"
|
||||
"list_no_split=config_list\n"
|
||||
"list_with_split=x,y,z\n"
|
||||
"normal=config_normal\n"
|
||||
)
|
||||
|
||||
loader = SettingsLoader(
|
||||
args={
|
||||
"no_override": ["arg1", "arg2"],
|
||||
"list_no_split": ["arg1", "arg2"],
|
||||
"list_with_split": ["p", "q", "r"],
|
||||
"normal": "arg_normal"
|
||||
},
|
||||
config_file=config_file
|
||||
)
|
||||
result = loader.load_settings(
|
||||
"Settings",
|
||||
{
|
||||
"no_override": ["args:no", "split:,"],
|
||||
"list_no_split": [],
|
||||
"list_with_split": ["split:,"],
|
||||
"normal": []
|
||||
}
|
||||
)
|
||||
|
||||
# Should use config value due to args:no with list args and split
|
||||
assert result["no_override"] == ["a", "b", "c"]
|
||||
# Should use config value because args is list without split
|
||||
assert result["list_no_split"] == "config_list"
|
||||
# Should use args value because split is defined and no args:no
|
||||
assert result["list_with_split"] == ["p", "q", "r"]
|
||||
# Should use args value normally (string arg without split)
|
||||
assert result["normal"] == "arg_normal"
|
||||
|
||||
captured = capsys.readouterr()
|
||||
# Should see override messages (even though list_no_split prints, it doesn't apply)
|
||||
assert "Command line option override" in captured.out
|
||||
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -153,6 +153,19 @@ class TestLogSettingsParsing:
|
||||
|
||||
assert log.log_settings["console_format_type"] == ConsoleFormatSettings.BARE
|
||||
|
||||
def test_parse_console_format_type_none(self, tmp_log_path: Path):
|
||||
"""Test parsing with console_format_type set to NONE"""
|
||||
settings: dict[str, Any] = {
|
||||
"console_format_type": ConsoleFormatSettings.NONE,
|
||||
}
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test",
|
||||
log_settings=settings # type: ignore
|
||||
)
|
||||
|
||||
assert log.log_settings["console_format_type"] == ConsoleFormatSettings.NONE
|
||||
|
||||
def test_parse_console_format_type_invalid(self, tmp_log_path: Path):
|
||||
"""Test parsing with invalid console_format_type raises TypeError"""
|
||||
settings: dict[str, Any] = {
|
||||
@@ -207,6 +220,11 @@ class TestConsoleFormatSettingsFromString:
|
||||
result = ConsoleFormatSettings.from_string('BARE')
|
||||
assert result == ConsoleFormatSettings.BARE
|
||||
|
||||
def test_from_string_none(self):
|
||||
"""Test from_string with 'NONE' returns correct format"""
|
||||
result = ConsoleFormatSettings.from_string('NONE')
|
||||
assert result == ConsoleFormatSettings.NONE
|
||||
|
||||
def test_from_string_invalid_returns_none(self):
|
||||
"""Test from_string with invalid string returns None"""
|
||||
result = ConsoleFormatSettings.from_string('INVALID')
|
||||
@@ -234,6 +252,7 @@ class TestConsoleFormatSettingsFromString:
|
||||
("CONDENSED", ConsoleFormatSettings.CONDENSED),
|
||||
("MINIMAL", ConsoleFormatSettings.MINIMAL),
|
||||
("BARE", ConsoleFormatSettings.BARE),
|
||||
("NONE", ConsoleFormatSettings.NONE),
|
||||
])
|
||||
def test_from_string_all_valid_settings(self, setting_name: str, expected: Any):
|
||||
"""Test from_string with all valid setting names"""
|
||||
|
||||
@@ -140,4 +140,172 @@ class TestCustomConsoleFormatter:
|
||||
assert "Critical message" in result
|
||||
assert "CRITICAL" in result
|
||||
|
||||
|
||||
# MARK: Test update_console_formatter
|
||||
class TestUpdateConsoleFormatter:
|
||||
"""Test cases for update_console_formatter method"""
|
||||
|
||||
def test_update_console_formatter_to_minimal(self, log_instance: Log):
|
||||
"""Test updating console formatter to MINIMAL format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_condensed(self, log_instance: Log):
|
||||
"""Test updating console formatter to CONDENSED format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.CONDENSED)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_bare(self, log_instance: Log):
|
||||
"""Test updating console formatter to BARE format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.BARE)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_none(self, log_instance: Log):
|
||||
"""Test updating console formatter to NONE format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.NONE)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_to_all(self, log_instance: Log):
|
||||
"""Test updating console formatter to ALL format"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.ALL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter was updated
|
||||
assert formatter is not None
|
||||
|
||||
def test_update_console_formatter_when_disabled(
|
||||
self, tmp_log_path: Path, basic_log_settings: LogSettings
|
||||
):
|
||||
"""Test that update_console_formatter does nothing when console is disabled"""
|
||||
# Disable console
|
||||
basic_log_settings['console_enabled'] = False
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test_log",
|
||||
log_settings=basic_log_settings
|
||||
)
|
||||
|
||||
# This should not raise an error and should return early
|
||||
log.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Verify console handler doesn't exist
|
||||
assert log.CONSOLE_HANDLER not in log.handlers
|
||||
|
||||
def test_update_console_formatter_with_color_enabled(
|
||||
self, tmp_log_path: Path, basic_log_settings: LogSettings
|
||||
):
|
||||
"""Test updating console formatter with color output enabled"""
|
||||
basic_log_settings['console_color_output_enabled'] = True
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test_log",
|
||||
log_settings=basic_log_settings
|
||||
)
|
||||
|
||||
log.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log.handlers[log.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter is CustomConsoleFormatter when colors enabled
|
||||
assert isinstance(formatter, CustomConsoleFormatter)
|
||||
|
||||
def test_update_console_formatter_without_color(self, log_instance: Log):
|
||||
"""Test updating console formatter without color output"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Get the console handler's formatter
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter = console_handler.formatter
|
||||
|
||||
# Verify formatter is standard Formatter when colors disabled
|
||||
assert isinstance(formatter, logging.Formatter)
|
||||
# But not the colored version
|
||||
assert not isinstance(formatter, CustomConsoleFormatter)
|
||||
|
||||
def test_update_console_formatter_multiple_times(self, log_instance: Log):
|
||||
"""Test updating console formatter multiple times"""
|
||||
# Update to MINIMAL
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
console_handler = log_instance.handlers[log_instance.CONSOLE_HANDLER]
|
||||
formatter1 = console_handler.formatter
|
||||
|
||||
# Update to CONDENSED
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.CONDENSED)
|
||||
formatter2 = console_handler.formatter
|
||||
|
||||
# Update to ALL
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.ALL)
|
||||
formatter3 = console_handler.formatter
|
||||
|
||||
# Verify each update created a new formatter
|
||||
assert formatter1 is not formatter2
|
||||
assert formatter2 is not formatter3
|
||||
assert formatter1 is not formatter3
|
||||
|
||||
def test_update_console_formatter_preserves_handler_level(self, log_instance: Log):
|
||||
"""Test that updating formatter preserves the handler's log level"""
|
||||
original_level = log_instance.handlers[log_instance.CONSOLE_HANDLER].level
|
||||
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
new_level = log_instance.handlers[log_instance.CONSOLE_HANDLER].level
|
||||
assert original_level == new_level
|
||||
|
||||
def test_update_console_formatter_format_output(
|
||||
self, log_instance: Log, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test that updated formatter actually affects log output"""
|
||||
# Set to BARE format (message only)
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.BARE)
|
||||
|
||||
# Configure caplog to capture at the appropriate level
|
||||
with caplog.at_level(logging.WARNING):
|
||||
log_instance.warning("Test warning message")
|
||||
|
||||
# Verify message was logged
|
||||
assert "Test warning message" in caplog.text
|
||||
|
||||
def test_update_console_formatter_none_format_output(
|
||||
self, log_instance: Log, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test that NONE formatter outputs only the message without any formatting"""
|
||||
# Set to NONE format (message only, no level indicator)
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.NONE)
|
||||
|
||||
# Configure caplog to capture at the appropriate level
|
||||
with caplog.at_level(logging.WARNING):
|
||||
log_instance.warning("Test warning message")
|
||||
|
||||
# Verify message was logged
|
||||
assert "Test warning message" in caplog.text
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -12,6 +12,7 @@ from corelibs.logging_handling.log import (
|
||||
LogParent,
|
||||
LogSettings,
|
||||
ConsoleFormatSettings,
|
||||
ConsoleFormat,
|
||||
)
|
||||
from corelibs.logging_handling.logging_level_handling.logging_level import LoggingLevel
|
||||
|
||||
@@ -108,4 +109,101 @@ class TestHandlerManagement:
|
||||
result2 = log.add_handler("test", handler2)
|
||||
assert result2 is False
|
||||
|
||||
def test_change_console_format_to_minimal(self, log_instance: Log):
|
||||
"""Test changing console handler format to MINIMAL"""
|
||||
original_formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
new_formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert new_formatter is not original_formatter
|
||||
assert new_formatter is not None
|
||||
|
||||
def test_change_console_format_to_condensed(self, log_instance: Log):
|
||||
"""Test changing console handler format to CONDENSED"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.CONDENSED)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_to_bare(self, log_instance: Log):
|
||||
"""Test changing console handler format to BARE"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.BARE)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_to_none(self, log_instance: Log):
|
||||
"""Test changing console handler format to NONE"""
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.NONE)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_to_all(self, log_instance: Log):
|
||||
"""Test changing console handler format to ALL"""
|
||||
# Start with a different format
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
log_instance.update_console_formatter(ConsoleFormatSettings.ALL)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
|
||||
def test_change_console_format_multiple_times(self, log_instance: Log):
|
||||
"""Test changing console handler format multiple times"""
|
||||
formatters: list[logging.Formatter | None] = []
|
||||
|
||||
for format_type in [
|
||||
ConsoleFormatSettings.MINIMAL,
|
||||
ConsoleFormatSettings.CONDENSED,
|
||||
ConsoleFormatSettings.BARE,
|
||||
ConsoleFormatSettings.NONE,
|
||||
ConsoleFormatSettings.ALL,
|
||||
]:
|
||||
log_instance.update_console_formatter(format_type)
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
formatters.append(formatter)
|
||||
assert formatter is not None
|
||||
|
||||
# Verify each formatter is unique (new instance each time)
|
||||
for i, formatter in enumerate(formatters):
|
||||
for j, other_formatter in enumerate(formatters):
|
||||
if i != j:
|
||||
assert formatter is not other_formatter
|
||||
|
||||
def test_change_console_format_with_disabled_console(
|
||||
self, tmp_log_path: Path, basic_log_settings: LogSettings
|
||||
):
|
||||
"""Test changing console format when console is disabled does nothing"""
|
||||
basic_log_settings['console_enabled'] = False
|
||||
log = Log(
|
||||
log_path=tmp_log_path,
|
||||
log_name="test_log",
|
||||
log_settings=basic_log_settings
|
||||
)
|
||||
|
||||
# Should not raise error, just return early
|
||||
log.update_console_formatter(ConsoleFormatSettings.MINIMAL)
|
||||
|
||||
# Console handler should not exist
|
||||
assert log.CONSOLE_HANDLER not in log.handlers
|
||||
|
||||
@pytest.mark.parametrize("format_type", [
|
||||
ConsoleFormatSettings.ALL,
|
||||
ConsoleFormatSettings.CONDENSED,
|
||||
ConsoleFormatSettings.MINIMAL,
|
||||
ConsoleFormatSettings.BARE,
|
||||
ConsoleFormatSettings.NONE,
|
||||
])
|
||||
def test_change_console_format_parametrized(
|
||||
self, log_instance: Log, format_type: ConsoleFormat # type: ignore
|
||||
):
|
||||
"""Test changing console format with all format types"""
|
||||
log_instance.update_console_formatter(format_type)
|
||||
|
||||
formatter = log_instance.handlers[log_instance.CONSOLE_HANDLER].formatter
|
||||
assert formatter is not None
|
||||
assert isinstance(formatter, logging.Formatter)
|
||||
|
||||
# __END__
|
||||
|
||||
Reference in New Issue
Block a user