Compare commits

..

12 Commits

Author SHA1 Message Date
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
Clemens Schwaighofer
447034046e v0.12.3: settings loader error message improvement 2025-07-14 16:50:36 +09:00
Clemens Schwaighofer
0770ac0bb4 Better error handling in the settings loader for entry not found in block 2025-07-14 16:49:37 +09:00
Clemens Schwaighofer
aa2fbd4f70 v0.12.2: Fix mandatory for settings loader 2025-07-14 16:25:21 +09:00
Clemens Schwaighofer
58c8447531 Settings loader mandatory fixes
- mandatory empty check if empty list ([''])
- skip regex check if replace value is None -> allowed empty as empty if not mandatory
2025-07-14 16:23:55 +09:00
Clemens Schwaighofer
bcca43d774 v0.12.1: settings loader update, regex constants added 2025-07-14 16:01:54 +09:00
6 changed files with 99 additions and 11 deletions

View File

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

View File

@@ -51,11 +51,14 @@ class SettingsLoader:
self.always_print = always_print self.always_print = always_print
# entries that have to be split # entries that have to be split
self.entry_split_char: dict[str, str] = {} self.entry_split_char: dict[str, str] = {}
# entries that should be converted
self.entry_convert: dict[str, str] = {} self.entry_convert: dict[str, str] = {}
# default set entries
self.entry_set_empty: dict[str, str | None] = {}
# config parser, load config file first # config parser, load config file first
self.config_parser: configparser.ConfigParser | None = self.__load_config_file() self.config_parser: configparser.ConfigParser | None = self.__load_config_file()
# all settings # 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 # remove file name and get base path and check
if not self.config_file.parent.is_dir(): if not self.config_file.parent.is_dir():
raise ValueError(f"Cannot find the config folder: {self.config_file.parent}") raise ValueError(f"Cannot find the config folder: {self.config_file.parent}")
@@ -63,7 +66,12 @@ class SettingsLoader:
self._check_settings_abort: bool = False self._check_settings_abort: bool = False
# MARK: load settings # 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 neutral settings loader
@@ -79,10 +87,14 @@ class SettingsLoader:
- in: the right side is another KEY value from the settings where this value must be inside - 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 - 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 - 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: Args:
config_id (str): what block to load 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: Returns:
dict[str, str]: key = value list dict[str, str]: key = value list
@@ -94,6 +106,15 @@ class SettingsLoader:
try: try:
# load all data as is, validation is done afterwards # load all data as is, validation is done afterwards
settings[config_id] = dict(self.config_parser[config_id]) settings[config_id] = dict(self.config_parser[config_id])
except KeyError as e:
if allow_not_exist is True:
return {}
self.__print(
f"[!] Cannot read [{config_id}] block in the {self.config_file}: {e}",
'CRITICAL', raise_exception=True
)
sys.exit(1)
try:
for key, checks in config_validate.items(): for key, checks in config_validate.items():
skip = True skip = True
split_char = self.DEFAULT_ELEMENT_SPLIT_CHAR split_char = self.DEFAULT_ELEMENT_SPLIT_CHAR
@@ -108,6 +129,8 @@ class SettingsLoader:
'CRITICAL', 'CRITICAL',
raise_exception=True raise_exception=True
) )
sys.exit(1)
self.entry_convert[key] = convert_to
except ValueError as e: except ValueError as e:
self.__print( self.__print(
f"[!] In [{config_id}] the convert type setup for entry failed: {check}: {e}", f"[!] In [{config_id}] the convert type setup for entry failed: {check}: {e}",
@@ -115,6 +138,20 @@ class SettingsLoader:
raise_exception=True raise_exception=True
) )
sys.exit(1) sys.exit(1)
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}")
self.__print(
f"[!] In [{config_id}] the empty set type for entry failed: {check}: {e}",
'CRITICAL',
raise_exception=True
)
sys.exit(1)
# split char, also check to not set it twice, first one only # split char, also check to not set it twice, first one only
if check.startswith("split:") and not self.entry_split_char.get(key): if check.startswith("split:") and not self.entry_split_char.get(key):
try: try:
@@ -145,7 +182,7 @@ class SettingsLoader:
] ]
except KeyError as e: except KeyError as e:
self.__print( self.__print(
f"[!] Cannot read [{config_id}] block in the {self.config_file}: {e}", f"[!] Cannot read [{config_id}] block because the entry [{e}] could not be found",
'CRITICAL', raise_exception=True 'CRITICAL', raise_exception=True
) )
sys.exit(1) sys.exit(1)
@@ -175,7 +212,9 @@ class SettingsLoader:
# - length: for string length # - length: for string length
# - range: for int/float range check # - range: for int/float range check
# mandatory check # mandatory check
if check == "mandatory:yes" and not settings[config_id].get(entry): if check == "mandatory:yes" and (
not settings[config_id].get(entry) or settings[config_id].get(entry) == ['']
):
error = True error = True
self.__print(f"[!] Missing content entry for: {entry}", 'ERROR') self.__print(f"[!] Missing content entry for: {entry}", 'ERROR')
# skip if empty none # skip if empty none
@@ -234,8 +273,14 @@ class SettingsLoader:
if error is True: if error is True:
self.__print("[!] Missing or incorrect settings data. Cannot proceed", 'CRITICAL', raise_exception=True) self.__print("[!] Missing or incorrect settings data. Cannot proceed", 'CRITICAL', raise_exception=True)
sys.exit(1) sys.exit(1)
# 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 # 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]): if convert_type in ["int", "any"] and is_int(settings[config_id][entry]):
settings[config_id][entry] = 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]): elif convert_type in ["float", "any"] and is_float(settings[config_id][entry]):
@@ -254,6 +299,7 @@ class SettingsLoader:
'ERROR' 'ERROR'
) )
# string is always string # string is always string
# TODO: empty and int/float/bool: set to none?
return settings[config_id] return settings[config_id]
@@ -322,6 +368,9 @@ class SettingsLoader:
(__from, __to, __equal) = check (__from, __to, __equal) = check
valid = True valid = True
for value_raw in convert_to_list(values): for value_raw in convert_to_list(values):
# skip no tset values for range check
if not value_raw:
continue
value = 0 value = 0
error_mark = '' error_mark = ''
if check_type == 'length': if check_type == 'length':
@@ -392,7 +441,8 @@ class SettingsLoader:
clean: re.Pattern[str] | None = None clean: re.Pattern[str] | None = None
if regex_clean is not None: if regex_clean is not None:
clean = re.compile(regex_clean, re.VERBOSE) clean = re.compile(regex_clean, re.VERBOSE)
if not check.search(value): # value must be set if clean is None, else empty value is allowed and will fail
if (clean is None and value or clean) and not check.search(value):
self.__print( self.__print(
f"[!] Invalid content for '{entry}' with check '{validate}' and data: {value}", f"[!] Invalid content for '{entry}' with check '{validate}' and data: {value}",
'ERROR', print_error 'ERROR', print_error

View File

@@ -3,6 +3,9 @@ Dict helpers
""" """
from typing import Any
def mask( def mask(
data_set: dict[str, str], data_set: dict[str, str],
mask_keys: list[str] | None = None, mask_keys: list[str] | None = None,
@@ -34,4 +37,22 @@ def mask(
for key, value in data_set.items() for key, value in data_set.items()
} }
def set_entry(dict_set: dict[str, Any], key: str, value_set: Any) -> dict[str, Any]:
"""
set a new entry in the dict set
Arguments:
key {str} -- _description_
dict_set {dict[str, Any]} -- _description_
value_set {Any} -- _description_
Returns:
dict[str, Any] -- _description_
"""
if not dict_set.get(key):
dict_set[key] = {}
dict_set[key] = value_set
return dict_set
# __END__ # __END__

View File

@@ -1,6 +1,7 @@
[TestA] [TestA]
foo=bar foo=bar
foobar=1 foobar=1
bar=st
some_match=foo some_match=foo
some_match_list=foo,bar some_match_list=foo,bar
test_list=a,b,c,d f, g h test_list=a,b,c,d f, g h
@@ -8,6 +9,8 @@ other_list=a|b|c|d|
third_list=xy|ab|df|fg third_list=xy|ab|df|fg
str_length=foobar str_length=foobar
int_range=20 int_range=20
int_range_not_set=
int_range_not_set_empty_set=5
# #
match_target=foo match_target=foo
match_target_list=foo,bar,baz match_target_list=foo,bar,baz
@@ -21,5 +24,6 @@ match_source_list=foo,bar
element_a=Static energy element_a=Static energy
element_b=123.5 element_b=123.5
element_c=True element_c=True
email=foo@bar.com,other+bar-fee@domain-com.cp email=foo@bar.com,other+bar-fee@domain-com.cp,
email_bad=@bar.com email_not_mandatory=
email_bad=gii@bar.com

View File

@@ -42,8 +42,10 @@ def main():
config_data = sl.load_settings( config_data = sl.load_settings(
config_load, config_load,
{ {
# "doesnt": ["split:,"],
"foo": ["mandatory:yes"], "foo": ["mandatory:yes"],
"foobar": ["check:int"], "foobar": ["check:int"],
"bar": ["mandatory:yes"],
"some_match": ["matching:foo|bar"], "some_match": ["matching:foo|bar"],
"some_match_list": ["split:,", "matching:foo|bar"], "some_match_list": ["split:,", "matching:foo|bar"],
"test_list": [ "test_list": [
@@ -61,6 +63,12 @@ def main():
"int_range": [ "int_range": [
"range:2-50" "range:2-50"
], ],
"int_range_not_set": [
"range:2-50"
],
"int_range_not_set_empty_set": [
"empty:"
],
"match_target": ["matching:foo"], "match_target": ["matching:foo"],
"match_target_list": ["split:,", "matching:foo|bar|baz",], "match_target_list": ["split:,", "matching:foo|bar|baz",],
"match_source_a": ["in:match_target"], "match_source_a": ["in:match_target"],
@@ -82,6 +90,11 @@ def main():
"mandatory:yes", "mandatory:yes",
"check:string.email.basic" "check:string.email.basic"
], ],
"email_not_mandatory": [
"split:,",
# "mandatory:yes",
"check:string.email.basic"
],
"email_bad": [ "email_bad": [
"split:,", "split:,",
"mandatory:yes", "mandatory:yes",

2
uv.lock generated
View File

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