Compare commits

...

9 Commits

Author SHA1 Message Date
Clemens Schwaighofer
7119844313 v0.12.6: Settings: exception raised on error point, stacklevel increased for all sub functions in log/settings loader 2025-07-15 09:51:23 +09:00
Clemens Schwaighofer
5763f57830 In settings loader do the raise ValueRror on the error, fix stack level, loggin fix stack level
Settings loader: all errors are thrown where the error happens and not in the print function
The print function if to log will add +1 to the stack level so the error is shown

In the log class in the log wrapper calls add +1 to the stack level to have the error line in the correct place
-> this fixes the stack trace part for now but we still want to have an auto full stack trace simple added
2025-07-15 09:44:29 +09:00
Clemens Schwaighofer
70e8ceecce v0.12.5: settings loader allow empty block 2025-07-14 18:15:59 +09:00
Clemens Schwaighofer
acbe1ac692 Settings load add info for future settings/options argument 2025-07-14 18:15:07 +09:00
Clemens Schwaighofer
99bca2c467 Allow settings block to not exist via call setting 2025-07-14 18:14:33 +09:00
Clemens Schwaighofer
b74ed1f30e v0.12.4: settings loader add set default value for empty 2025-07-14 17:22:03 +09:00
Clemens Schwaighofer
8082ab78a1 Merge branch 'development' 2025-07-14 17:21:28 +09:00
Clemens Schwaighofer
c69076f517 Add set default if empty/not set in settings
With new empty: block if just like this set to None if not set (empty), can also be any value,
if list, skip setting default
2025-07-14 17:21:04 +09:00
Clemens Schwaighofer
648ab001b6 Settings loader fix for not set range check entries
If we have a range or length check and the value is not set, skip, and do not convert either
Not set is None
2025-07-14 17:00:25 +09:00
6 changed files with 102 additions and 71 deletions

View File

@@ -1,7 +1,7 @@
# MARK: Project info
[project]
name = "corelibs"
version = "0.12.3"
version = "0.12.6"
description = "Collection of utils for Python scripts"
readme = "README.md"
requires-python = ">=3.13"

View File

@@ -5,7 +5,6 @@ Additional check for override settings as arguments
"""
import re
import sys
import configparser
from typing import Any, Tuple, Sequence, cast
from pathlib import Path
@@ -51,11 +50,14 @@ class SettingsLoader:
self.always_print = always_print
# entries that have to be split
self.entry_split_char: dict[str, str] = {}
# entries that should be converted
self.entry_convert: dict[str, str] = {}
# default set entries
self.entry_set_empty: dict[str, str | None] = {}
# config parser, load config file first
self.config_parser: configparser.ConfigParser | None = self.__load_config_file()
# all settings
self.settings: dict[str, dict[str, Any]] | None = None
self.settings: dict[str, dict[str, None | str | int | float | bool]] | None = None
# remove file name and get base path and check
if not self.config_file.parent.is_dir():
raise ValueError(f"Cannot find the config folder: {self.config_file.parent}")
@@ -63,7 +65,12 @@ class SettingsLoader:
self._check_settings_abort: bool = False
# MARK: load settings
def load_settings(self, config_id: str, config_validate: dict[str, list[str]]) -> dict[str, str]:
def load_settings(
self,
config_id: str,
config_validate: dict[str, list[str]],
allow_not_exist: bool = False
) -> dict[str, str]:
"""
neutral settings loader
@@ -79,10 +86,14 @@ class SettingsLoader:
- in: the right side is another KEY value from the settings where this value must be inside
- split: character to split entries, if set check:list+ must be set if checks are needed
- convert: convert to int, float -> if element is number convert, else leave as is
- empty: convert empty to, if nothing set on the right side then convert to None type
TODO: there should be a config/options argument for general settings
Args:
config_id (str): what block to load
config_allowed (list[str]): list of allowed entries sets
config_validate (dict[str, list[str]]): list of allowed entries sets
allow_not_exist (bool): If set to True, does not throw an error, but returns empty set
Returns:
dict[str, str]: key = value list
@@ -95,11 +106,12 @@ class SettingsLoader:
# load all data as is, validation is done afterwards
settings[config_id] = dict(self.config_parser[config_id])
except KeyError as e:
self.__print(
if allow_not_exist is True:
return {}
raise ValueError(self.__print(
f"[!] Cannot read [{config_id}] block in the {self.config_file}: {e}",
'CRITICAL', raise_exception=True
)
sys.exit(1)
'CRITICAL'
)) from e
try:
for key, checks in config_validate.items():
skip = True
@@ -110,18 +122,28 @@ class SettingsLoader:
try:
[_, convert_to] = check.split(":")
if convert_to not in self.CONVERT_TO_LIST:
self.__print(
raise ValueError(self.__print(
f"[!] In [{config_id}] the convert type is invalid {check}: {convert_to}",
'CRITICAL',
raise_exception=True
)
'CRITICAL'
))
self.entry_convert[key] = convert_to
except ValueError as e:
self.__print(
raise ValueError(self.__print(
f"[!] In [{config_id}] the convert type setup for entry failed: {check}: {e}",
'CRITICAL',
raise_exception=True
)
sys.exit(1)
'CRITICAL'
)) from e
if check.startswith('empty:'):
try:
[_, empty_set] = check.split(":")
if not empty_set:
empty_set = None
self.entry_set_empty[key] = empty_set
except ValueError as e:
print(f"VALUE ERROR: {key}")
raise ValueError(self.__print(
f"[!] In [{config_id}] the empty set type for entry failed: {check}: {e}",
'CRITICAL'
)) from e
# split char, also check to not set it twice, first one only
if check.startswith("split:") and not self.entry_split_char.get(key):
try:
@@ -138,12 +160,10 @@ class SettingsLoader:
self.entry_split_char[key] = split_char
skip = False
except ValueError as e:
self.__print(
raise ValueError(self.__print(
f"[!] In [{config_id}] the split character setup for entry failed: {check}: {e}",
'CRITICAL',
raise_exception=True
)
sys.exit(1)
'CRITICAL'
)) from e
if skip:
continue
settings[config_id][key] = [
@@ -151,16 +171,14 @@ class SettingsLoader:
for __value in settings[config_id][key].split(split_char)
]
except KeyError as e:
self.__print(
raise ValueError(self.__print(
f"[!] Cannot read [{config_id}] block because the entry [{e}] could not be found",
'CRITICAL', raise_exception=True
)
sys.exit(1)
'CRITICAL'
)) from e
else:
# ignore error if arguments are set
if not self.__check_arguments(config_validate, True):
self.__print(f"[!] Cannot find file: {self.config_file}", 'CRITICAL', raise_exception=True)
sys.exit(1)
raise ValueError(self.__print(f"[!] Cannot find file: {self.config_file}", 'CRITICAL'))
else:
# base set
settings[config_id] = {}
@@ -241,10 +259,15 @@ class SettingsLoader:
):
error = True
if error is True:
self.__print("[!] Missing or incorrect settings data. Cannot proceed", 'CRITICAL', raise_exception=True)
sys.exit(1)
raise ValueError(self.__print("[!] Missing or incorrect settings data. Cannot proceed", 'CRITICAL'))
# set empty
for [entry, empty_set] in self.entry_set_empty.items():
# if set, skip, else set to empty value
if settings[config_id].get(entry) or isinstance(settings[config_id].get(entry), list):
continue
settings[config_id][entry] = empty_set
# Convert input
for [entry, convert_type] in self.entry_convert:
for [entry, convert_type] in self.entry_convert.items():
if convert_type in ["int", "any"] and is_int(settings[config_id][entry]):
settings[config_id][entry] = int(settings[config_id][entry])
elif convert_type in ["float", "any"] and is_float(settings[config_id][entry]):
@@ -263,6 +286,7 @@ class SettingsLoader:
'ERROR'
)
# string is always string
# TODO: empty and int/float/bool: set to none?
return settings[config_id]
@@ -289,22 +313,20 @@ class SettingsLoader:
try:
[__from, __to] = check.split('-')
if (__from and not is_float(__from)) or (__to and not is_float(__to)):
self.__print(
raise ValueError(self.__print(
f"[{entry}] Check value for length is not in: {check}",
'CRITICAL', raise_exception=True
)
sys.exit(1)
'CRITICAL'
))
if len(__from) == 0:
__from = None
if len(__to) == 0:
__to = None
except ValueError:
except ValueError as e:
if not is_float(__equal := check):
self.__print(
raise ValueError(self.__print(
f"[{entry}] Check value for length is not a valid integer: {check}",
'CRITICAL', raise_exception=True
)
sys.exit(1)
'CRITICAL'
)) from e
if len(__equal) == 0:
__equal = None
# makre sure this is all int or None
@@ -331,6 +353,9 @@ class SettingsLoader:
(__from, __to, __equal) = check
valid = True
for value_raw in convert_to_list(values):
# skip no tset values for range check
if not value_raw:
continue
value = 0
error_mark = ''
if check_type == 'length':
@@ -437,11 +462,10 @@ class SettingsLoader:
# get the check settings
__check_settings = SettingsLoaderCheck.CHECK_SETTINGS.get(check)
if __check_settings is None:
self.__print(
raise ValueError(self.__print(
f"[{entry}] Cannot get SettingsLoaderCheck.CHECK_SETTINGS for {check}",
'CRITICAL', raise_exception=True
)
sys.exit(1)
'CRITICAL'
))
# either removes or replaces invalid characters in the list
if isinstance(setting_value, list):
# clean up invalid characters
@@ -509,7 +533,7 @@ class SettingsLoader:
return self.args.get(entry)
# MARK: error print
def __print(self, msg: str, level: str, print_error: bool = True, raise_exception: bool = False):
def __print(self, msg: str, level: str, print_error: bool = True) -> str:
"""
print out error, if Log class is set then print to log instead
@@ -523,12 +547,11 @@ class SettingsLoader:
if self.log is not None:
if not Log.validate_log_level(level):
level = 'ERROR'
self.log.logger.log(Log.get_log_level_int(level), msg)
self.log.logger.log(Log.get_log_level_int(level), msg, stacklevel=2)
if self.log is None or self.always_print:
if print_error:
print(msg)
if raise_exception:
raise ValueError(msg)
return msg
# __END__

View File

@@ -74,8 +74,8 @@ class CustomConsoleFormatter(logging.Formatter):
return f"{color}{message}{reset}"
# TODO: add custom handlers for stack_correct, if not set fill with %(filename)s:%(funcName)s:%(lineno)d
# hasattr(record, 'stack_correct')
# TODO: add custom handlers for stack_trace, if not set fill with %(filename)s:%(funcName)s:%(lineno)d
# hasattr(record, 'stack_trace')
# MARK: Log class
@@ -367,7 +367,7 @@ class Log:
return root_logger
# FIXME: all below will only work if we add a custom format interface for the stack_correct part
# FIXME: we need to add a custom formater to add stack level listing if we want to
# Important note, although they exist, it is recommended to use self.logger.NAME directly
# so that the correct filename, method and row number is set
# for > 50 use logger.log(LoggingLevel.<LEVEL>.value, ...)
@@ -379,8 +379,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.log(level, msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.log(level, msg, *args, extra=extra, stacklevel=2)
# MARK: DEBUG 10
def debug(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -389,8 +389,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.debug(msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.debug(msg, *args, extra=extra, stacklevel=2)
# MARK: INFO 20
def info(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -399,8 +399,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.info(msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.info(msg, *args, extra=extra, stacklevel=2)
# MARK: WARNING 30
def warning(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -409,8 +409,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.warning(msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.warning(msg, *args, extra=extra, stacklevel=2)
# MARK: ERROR 40
def error(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -419,8 +419,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.error(msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.error(msg, *args, extra=extra, stacklevel=2)
# MARK: CRITICAL 50
def critical(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -429,8 +429,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.critical(msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.critical(msg, *args, extra=extra, stacklevel=2)
# MARK: ALERT 55
def alert(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -440,8 +440,8 @@ class Log:
# extra_dict = dict(extra)
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.log(LoggingLevel.ALERT.value, msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.log(LoggingLevel.ALERT.value, msg, *args, extra=extra, stacklevel=2)
# MARK: EMERGECNY: 60
def emergency(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -450,8 +450,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.log(LoggingLevel.EMERGENCY.value, msg, *args, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.log(LoggingLevel.EMERGENCY.value, msg, *args, extra=extra, stacklevel=2)
# MARK: EXCEPTION: 70
def exception(self, msg: object, *args: object, extra: MutableMapping[str, object] | None = None) -> None:
@@ -467,8 +467,8 @@ class Log:
raise ValueError('Logger is not yet initialized')
if extra is None:
extra = {}
extra['stack_correct'] = traceback_call_str(start=3)
self.logger.log(LoggingLevel.EXCEPTION.value, msg, *args, exc_info=True, extra=extra)
extra['stack_trace'] = traceback_call_str(start=3)
self.logger.log(LoggingLevel.EXCEPTION.value, msg, *args, exc_info=True, extra=extra, stacklevel=2)
# MARK: break line
def break_line(self, info: str = "BREAK"):

View File

@@ -9,6 +9,8 @@ other_list=a|b|c|d|
third_list=xy|ab|df|fg
str_length=foobar
int_range=20
int_range_not_set=
int_range_not_set_empty_set=5
#
match_target=foo
match_target_list=foo,bar,baz

View File

@@ -63,6 +63,12 @@ def main():
"int_range": [
"range:2-50"
],
"int_range_not_set": [
"range:2-50"
],
"int_range_not_set_empty_set": [
"empty:"
],
"match_target": ["matching:foo"],
"match_target_list": ["split:,", "matching:foo|bar|baz",],
"match_source_a": ["in:match_target"],

2
uv.lock generated
View File

@@ -44,7 +44,7 @@ wheels = [
[[package]]
name = "corelibs"
version = "0.12.2"
version = "0.12.5"
source = { editable = "." }
dependencies = [
{ name = "jmespath" },