From a9b53d7f6c5da91124962c19501b554cbb9f1b25 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Wed, 11 Oct 2023 14:27:02 +0200 Subject: [PATCH 01/13] add python bindings --- bindings/python3/econf/__init__.py | 3 + bindings/python3/econf/econf.py | 795 +++++++++++++++++++++++++++++ bindings/python3/pyproject.toml | 19 + 3 files changed, 817 insertions(+) create mode 100644 bindings/python3/econf/__init__.py create mode 100644 bindings/python3/econf/econf.py create mode 100644 bindings/python3/pyproject.toml diff --git a/bindings/python3/econf/__init__.py b/bindings/python3/econf/__init__.py new file mode 100644 index 0000000..5c66dfb --- /dev/null +++ b/bindings/python3/econf/__init__.py @@ -0,0 +1,3 @@ +from econf.econf import * +from econf.econf import EconfFile +from econf.econf import _encode_str, _check_int_overflow, _check_uint_overflow, _check_float_overflow diff --git a/bindings/python3/econf/econf.py b/bindings/python3/econf/econf.py new file mode 100644 index 0000000..b49a336 --- /dev/null +++ b/bindings/python3/econf/econf.py @@ -0,0 +1,795 @@ +""" +Econf provides functionality for interacting with Key-Value config files, like getting and setting values for read config files. + +For more information please have a look at the API +""" +import ctypes.util +from enum import Enum +from dataclasses import dataclass +from typing import * +from ctypes import * + +LIBNAME = ctypes.util.find_library("econf") +LIBECONF = CDLL(LIBNAME) + + +@dataclass +class EconfFile: + """ + Class which points to the Key Value storage object + """ + + _ptr: c_void_p + + def __del__(self): + free_file(self) + + +class Econf_err(Enum): + ECONF_SUCCESS = 0 + ECONF_ERROR = 1 + ECONF_NOMEM = 2 + ECONF_NOFILE = 3 + ECONF_NOGROUP = 4 + ECONF_NOKEY = 5 + ECONF_EMPTYKEY = 6 + ECONF_WRITEERROR = 7 + ECONF_PARSE_ERROR = 8 + ECONF_MISSING_BRACKET = 9 + ECONF_MISSING_DELIMITER = 10 + ECONF_EMPTY_SECTION_NAME = 11 + ECONF_TEXT_AFTER_SECTION = 12 + ECONF_FILE_LIST_IS_NULL = 13 + ECONF_WRONG_BOOLEAN_VALUE = 14 + ECONF_KEY_HAS_NULL_VALUE = 15 + ECONF_WRONG_OWNER = 16 + ECONF_WRONG_GROUP = 17 + ECONF_WRONG_FILE_PERMISSION = 18 + ECONF_WRONG_DIR_PERMISSION = 19 + ECONF_ERROR_FILE_IS_SYM_LINK = 20 + ECONF_PARSING_CALLBACK_FAILED = 21 + + +def _exceptions(err: int, val: str): + if err == 1: + raise Exception(val) + elif err == 2: + raise MemoryError(val) + elif err == 3: + raise FileNotFoundError(val) + elif err == 4: + raise KeyError(val) + elif err == 5: + raise KeyError(val) + elif err == 6: + raise KeyError(val) + elif err == 7: + raise OSError(val) + elif err == 8: + raise Exception(val) + elif err == 9: + raise SyntaxError(val) + elif err == 10: + raise SyntaxError(val) + elif err == 11: + raise SyntaxError(val) + elif err == 12: + raise SyntaxError(val) + elif err == 13: + raise ValueError(val) + elif err == 14: + raise ValueError(val) + elif err == 15: + raise ValueError(val) + elif err == 16: + raise PermissionError(val) + elif err == 17: + raise PermissionError(val) + elif err == 18: + raise PermissionError(val) + elif err == 19: + raise PermissionError(val) + elif err == 20: + raise FileNotFoundError(val) + elif err == 21: + raise Exception(val) + else: + raise Exception(val) + + +def _encode_str(string: str | bytes) -> bytes: + if isinstance(string, str): + string = string.encode("utf-8") + elif not isinstance(string, bytes): + raise TypeError("Input must be a string or bytes") + return string + + +def _check_int_overflow(val: int) -> bool: + if isinstance(val, int): + c_val = c_int64(val) + return c_val.value == val + else: + raise TypeError("parameter is not an integer") + + +def _check_uint_overflow(val: int) -> bool: + if isinstance(val, int) & (val >= 0): + c_val = c_uint64(val) + return c_val.value == val + else: + raise TypeError("parameter is not a unsigned integer") + + +def _check_float_overflow(val: float) -> bool: + if isinstance(val, float): + c_val = c_double(val) + return c_val.value == val + else: + raise TypeError("parameter is not a float") + + +def set_value( + ef: EconfFile, group: str | bytes, key: str | bytes, value: Any +) -> Econf_err: + """ + Dynamically set a value in a keyfile and returns a status code + + :param ef: EconfFile object to set value in + :param group: group of the key to be changed + :param key: key to be changed + :param value: desired value + :return: Error code + """ + if isinstance(value, int): + if value >= 0: + res = set_uint_value(ef, group, key, value) + else: + res = set_int_value(ef, group, key, value) + elif isinstance(value, float): + res = set_float_value(ef, group, key, value) + elif isinstance(value, str) | isinstance(value, bytes): + res = set_string_value(ef, group, key, value) + elif isinstance(value, bool): + res = set_bool_value(ef, group, key, value) + else: + raise TypeError("'value' parameter is not one of the supported types") + return res + + +def read_file( + file_name: str | bytes, delim: str | bytes, comment: str | bytes +) -> EconfFile: + """ + Read a config file and write the key-value pairs into a keyfile object + + :param file_name: absolute path of file to be parsed + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :return: Key-Value storage object + """ + result = EconfFile(c_void_p(None)) + file_name = _encode_str(file_name) + delim = _encode_str(delim) + comment = _encode_str(comment) + err = LIBECONF.econf_readFile( + byref(result._ptr), file_name, delim, comment + ) + if err: + raise _exceptions(err, f"read_file failed with error: {err_string(err)}") + return result + + +def merge_files(usr_file: EconfFile, etc_file: EconfFile) -> EconfFile: + """ + Merge the content of 2 keyfile objects + + :param usr_file: first EconfFile object + :param etc_file: second EconfFile object + :return: merged EconfFile object + """ + merged_file = EconfFile(c_void_p()) + err = LIBECONF.econf_mergeFiles( + byref(merged_file._ptr), + usr_file._ptr, + etc_file._ptr, + ) + if err: + raise _exceptions(err, f"merge_files failed with error: {err_string(err)}") + return merged_file + + +# this reads either the first OR the second file if the first one does not exist +def read_dirs( + usr_conf_dir: str | bytes, + etc_conf_dir: str | bytes, + project_name: str | bytes, + config_suffix: str | bytes, + delim: str | bytes, + comment: str | bytes, +) -> EconfFile: + """ + Read configuration from the first found config file and merge with snippets from conf.d/ directory + + e.g. searches /usr/etc/ and /etc/ for an example.conf file and merges it with the snippets in either + /usr/etc/example.conf.d/ or /etc/example.conf.d + + :param usr_conf_dir: absolute path of the first directory to be searched + :param etc_conf_dir: absolute path of the second directory to be searched + :param project_name: basename of the configuration file + :param config_suffix: suffix of the configuration file + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :return: merged EconfFile object + """ + result = EconfFile(c_void_p()) + c_usr_conf_dir = _encode_str(usr_conf_dir) + c_etc_conf_dir = _encode_str(etc_conf_dir) + c_project_name = _encode_str(project_name) + c_config_suffix = _encode_str(config_suffix) + err = LIBECONF.econf_readDirs( + byref(result._ptr), + c_usr_conf_dir, + c_etc_conf_dir, + c_project_name, + c_config_suffix, + delim, + comment, + ) + if err: + raise _exceptions(err, f"read_dirs failed with error: {err_string(err)}") + return result + + +# this reads either the first OR the second file if the first one does not exist +def read_dirs_history( + usr_conf_dir: str | bytes, + etc_conf_dir: str | bytes, + project_name: str | bytes, + config_suffix: str | bytes, + delim: str | bytes, + comment: str | bytes, +) -> list[EconfFile]: + """ + Read configuration from the first found config file and snippets from conf.d/ directory + + e.g. searches /usr/etc/ and /etc/ for an example.conf file and the snippets in either + /usr/etc/example.conf.d/ or /etc/example.conf.d + + :param usr_conf_dir: absolute path of the first directory to be searched + :param etc_conf_dir: absolute path of the second directory to be searched + :param project_name: basename of the configuration file + :param config_suffix: suffix of the configuration file + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :return: list of EconfFile objects + """ + key_files = c_void_p(None) + c_size = c_size_t() + c_usr_conf_dir = _encode_str(usr_conf_dir) + c_etc_conf_dir = _encode_str(etc_conf_dir) + c_project_name = _encode_str(project_name) + c_config_suffix = _encode_str(config_suffix) + err = LIBECONF.econf_readDirsHistory( + byref(key_files), + byref(c_size), + c_usr_conf_dir, + c_etc_conf_dir, + c_project_name, + c_config_suffix, + delim, + comment, + ) + if err: + raise _exceptions( + err, f"read_dirs_history failed with error: {err_string(err)}" + ) + arr = cast(key_files, POINTER(c_void_p * c_size.value)) + result = [EconfFile(c_void_p(i)) for i in arr.contents] + return result + + +def new_key_file(delim: str | bytes, comment: str | bytes) -> EconfFile: + """ + Create a new empty keyfile + + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :return: created EconfFile object + """ + result = EconfFile(c_void_p()) + delim = _encode_str(delim) + comment = _encode_str(comment) + err = LIBECONF.econf_newKeyFile(byref(result._ptr), delim, comment) + if err: + raise _exceptions(err, f"new_key_file failed with error: {err_string(err)}") + return result + + +def new_ini_file() -> EconfFile: + """ + Create a new empty keyfile with delimiter '=' and comment '#' + + :return: created EconfFile object + """ + result = EconfFile(c_void_p()) + err = LIBECONF.econf_newIniFile(byref(result._ptr)) + if err: + raise _exceptions(err, f"new_ini_file failed with error: {err_string(err)}") + return result + + +def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> Econf_err: + """ + Write content of a keyfile to specified location + + :param ef: Key-Value storage object + :param save_to_dir: directory into which the file has to be written + :param file_name: filename with suffix of the to be written file + :return: Error code + """ + c_save_to_dir = _encode_str(save_to_dir) + c_file_name = _encode_str(file_name) + err = LIBECONF.econf_writeFile( + byref(ef._ptr), c_save_to_dir, c_file_name + ) + return Econf_err(err) + + +def get_path(ef: EconfFile) -> str: + """ + Get the path of the source of the given key file + + :param ef: Key-Value storage object + :return: path of the config file as string + """ + # extract from pointer + LIBECONF.econf_getPath.restype = c_char_p + return LIBECONF.econf_getPath(ef._ptr).decode("utf-8") + + +def get_groups(ef: EconfFile) -> list[str]: + """ + List all the groups of given keyfile + + :param ef: Key-Value storage object + :return: list of groups in the keyfile + """ + c_length = c_size_t() + c_groups = c_void_p(None) + err = LIBECONF.econf_getGroups(ef._ptr, byref(c_length), byref(c_groups)) + if err: + _exceptions(err, f"get_groups failed with error: {err_string(err)}") + arr = cast(c_groups, POINTER(c_char_p * c_length.value)) + result = [i.decode("utf-8") for i in arr.contents] + return result + + +def get_keys(ef: EconfFile, group: str) -> list[str]: + """ + List all the keys of a given group or all keys in a keyfile + + :param ef: Key-Value storage object + :param group: group of the keys to be returned or None for keys without a group + :return: list of keys in the given group + """ + c_length = c_size_t() + c_keys = c_void_p(None) + if group: + group = _encode_str(group) + err = LIBECONF.econf_getKeys( + ef._ptr, group, byref(c_length), byref(c_keys) + ) + if err: + _exceptions(err, f"get_keys failed with error: {err_string(err)}") + arr = cast(c_keys, POINTER(c_char_p * c_length.value)) + result = [i.decode("utf-8") for i in arr.contents] + return result + + +def get_int_value(ef: EconfFile, group: str, key: str) -> int: + """ + Return an integer value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_int64() + err = LIBECONF.econf_getInt64Value( + ef._ptr, c_group, c_key, byref(c_result) + ) + if err: + _exceptions(err, f"get_int64_value failed with error: {err_string(err)}") + return c_result.value + + +def get_uint_value(ef: EconfFile, group: str, key: str) -> int: + """ + Return an unsigned integer value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_uint64() + err = LIBECONF.econf_getUInt64Value( + ef._ptr, c_group, c_key, byref(c_result) + ) + if err: + _exceptions(err, f"get_uint64_value failed with error: {err_string(err)}") + return c_result.value + + +def get_float_value(ef: EconfFile, group: str, key: str) -> float: + """ + Return a float value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_double() + err = LIBECONF.econf_getDoubleValue( + ef._ptr, c_group, c_key, byref(c_result) + ) + if err: + _exceptions(err, f"get_double_value failed with error: {err_string(err)}") + return c_result.value + + +def get_string_value(ef: EconfFile, group: str, key: str) -> str: + """ + Return a string value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_char_p() + err = LIBECONF.econf_getStringValue( + ef._ptr, c_group, c_key, byref(c_result) + ) + if err: + _exceptions(err, f"get_string_value failed with error: {err_string(err)}") + return c_result.value.decode("utf-8") + + +def get_bool_value(ef: EconfFile, group: str, key: str) -> bool: + """ + Return a boolean value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_bool() + err = LIBECONF.econf_getBoolValue( + ef._ptr, c_group, c_key, byref(c_result) + ) + if err: + _exceptions(err, f"get_bool_value failed with error: {err_string(err)}") + return c_result.value + + +def get_int_value_def(ef: EconfFile, group: str, key: str, default: int) -> int: + """ + Return an integer value for given group/key or return a default value if key is not found + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param default: value to be returned if no key is found + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_int64() + if not isinstance(default, int): + raise TypeError(f'"default" parameter must be of type int') + if not _check_int_overflow(default): + raise ValueError( + f"Integer overflow found, only up to 64 bit integers are supported" + ) + c_default = c_int64(default) + err = LIBECONF.econf_getInt64ValueDef( + ef._ptr, c_group, c_key, byref(c_result), c_default + ) + if err: + if err == 5: + return c_default.value + _exceptions(err, f"get_int64_value_def failed with error: {err_string(err)}") + return c_result.value + + +def get_uint_value_def(ef: EconfFile, group: str, key: str, default: int) -> int: + """ + Return an unsigned integer value for given group/key or return a default value if key is not found + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param default: value to be returned if no key is found + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_uint64() + if not isinstance(default, int) | (default < 0): + raise TypeError( + f'"default" parameter must be of type int and greater or equal to zero' + ) + if not _check_uint_overflow(default): + raise ValueError( + f"Integer overflow found, only up to 64 bit unsigned integers are supported" + ) + c_default = c_uint64(default) + err = LIBECONF.econf_getUInt64ValueDef( + ef._ptr, c_group, c_key, byref(c_result), c_default + ) + if err: + if err == 5: + return c_default.value + _exceptions(err, f"get_uint64_value_def failed with error: {err_string(err)}") + return c_result.value + + +def get_float_value_def(ef: EconfFile, group: str, key: str, default: float) -> float: + """ + Return a float value for given group/key or return a default value if key is not found + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param default: value to be returned if no key is found + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_double() + if not isinstance(default, float): + raise TypeError(f'"default" parameter must be of type float') + if not _check_float_overflow(default): + raise ValueError( + f"Float overflow found, only up to 64 bit floats are supported" + ) + c_default = c_double(default) + err = LIBECONF.econf_getDoubleValueDef( + ef._ptr, c_group, c_key, byref(c_result), c_default + ) + if err: + if err == 5: + return c_default.value + _exceptions(err, f"get_double_value_def failed with error: {err_string(err)}") + return c_result.value + + +def get_string_value_def(ef: EconfFile, group: str, key: str, default: str) -> str: + """ + Return a string value for given group/key or return a default value if key is not found + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param default: value to be returned if no key is found + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_char_p() + c_default = _encode_str(default) + err = LIBECONF.econf_getStringValueDef( + ef._ptr, c_group, c_key, byref(c_result), c_default + ) + if err: + if err == 5: + return c_default.decode("utf-8") + _exceptions(err, f"get_string_value_def failed with error: {err_string(err)}") + return c_result.value.decode("utf-8") + + +def get_bool_value_def(ef: EconfFile, group: str, key: str, default: bool) -> bool: + """ + Return a boolean value for given group/key or return a default value if key is not found + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param default: value to be returned if no key is found + :return: value of the key + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_result = c_bool() + if not isinstance(default, bool): + raise TypeError(f'"value" parameter must be of type bool') + c_default = c_bool(default) + err = LIBECONF.econf_getBoolValueDef( + ef._ptr, c_group, c_key, byref(c_result), c_default + ) + if err: + if err == 5: + return c_default.value + _exceptions(err, f"get_bool_value_def failed with error: {err_string(err)}") + return c_result.value + + +def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: + """ + Setting an integer value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param value: value to be set for given key + :return: Error code + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + if not isinstance(value, int): + raise TypeError(f'"value" parameter must be of type int') + if not _check_int_overflow(value): + raise ValueError( + f"Integer overflow found, only up to 64 bit integers are supported" + ) + c_value = c_int64(value) + err = LIBECONF.econf_setInt64Value( + byref(ef._ptr), c_group, c_key, c_value + ) + if err: + _exceptions(err, f"set_int64_value failed with error: {err_string(err)}") + return Econf_err(err) + + +def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: + """ + Setting an unsigned integer value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param value: value to be set for given key + :return: Error code + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + if not isinstance(value, int) | (value < 0): + raise TypeError( + f'"value" parameter must be of type int and be greater or equal to zero' + ) + if not _check_uint_overflow(value): + raise ValueError( + f"Integer overflow found, only up to 64 bit unsigned integers are supported" + ) + c_value = c_uint64(value) + err = LIBECONF.econf_setUInt64Value( + byref(ef._ptr), c_group, c_key, c_value + ) + if err: + _exceptions(err, f"set_uint64_value failed with error: {err_string(err)}") + return Econf_err(err) + + +def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> Econf_err: + """ + Setting a float value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param value: value to be set for given key + :return: Error code + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + if not isinstance(value, float): + raise TypeError(f'"value" parameter must be of type float') + if not _check_float_overflow(value): + raise ValueError( + f"Float overflow found, only up to 64 bit floats are supported" + ) + c_value = c_double(value) + err = LIBECONF.econf_setDoubleValue( + byref(ef._ptr), c_group, c_key, c_value + ) + if err: + _exceptions(err, f"set_double_value failed with error: {err_string(err)}") + return Econf_err(err) + + +def set_string_value( + ef: EconfFile, group: str, key: str, value: str | bytes +) -> Econf_err: + """ + Setting a string value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param value: value to be set for given key + :return: Error code + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + c_value = _encode_str(value) + err = LIBECONF.econf_setStringValue( + byref(ef._ptr), c_group, c_key, c_value + ) + if err: + _exceptions(err, f"set_string_value failed with error: {err_string(err)}") + return Econf_err(err) + + +def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> Econf_err: + """ + Setting a boolean value for given group/key + + :param ef: Key-Value storage object + :param group: desired group + :param key: key of the value that is requested + :param value: value to be set for given key + :return: Error code + """ + c_group = _encode_str(group) + c_key = _encode_str(key) + if not isinstance(value, bool): + raise TypeError(f'"value" parameter must be of type bool') + c_value = c_bool(value) + err = LIBECONF.econf_setBoolValue( + byref(ef._ptr), c_group, c_key, c_value + ) + if err: + _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") + return Econf_err(err) + + +def err_string(error: int): + """ + Convert an error code into error message + + :param error: error code as integer + :return: error string + """ + if not isinstance(error, int): + raise TypeError("Error codes must be of type int") + c_int(error) + LIBECONF.econf_errString.restype = c_char_p + return LIBECONF.econf_errString(error).decode("utf-8") + + +def err_location(): + """ + Info about the line where an error happened + + :return: path to the last handled file and number of last handled line + """ + c_filename = c_char_p() + c_line_nr = c_uint64() + LIBECONF.econf_errLocation(byref(c_filename), byref(c_line_nr)) + return c_filename.value, c_line_nr.value + + +def free_file(ef: EconfFile): + """ + Free the memory of a given keyfile + + :param ef: EconfFile to be freed + :return: None + """ + LIBECONF.econf_freeFile(ef._ptr) + return diff --git a/bindings/python3/pyproject.toml b/bindings/python3/pyproject.toml new file mode 100644 index 0000000..47883bd --- /dev/null +++ b/bindings/python3/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "python-libeconf" +version = "1.0.0" +description = "Python bindings for libeconf" +authors = [{name="nkrapp", email="nico.krapp@suse.com"}] +readme = "README.md" + +[project.optional-dependencies] +doc_requires = [ + "sphinx", + "sphinx-rtd-theme" +] + +[tool.isort] +profile = "black" From 928be08be4adebbd34e1be90931ccd8db8ae3b5c Mon Sep 17 00:00:00 2001 From: nkrapp Date: Thu, 12 Oct 2023 13:08:53 +0200 Subject: [PATCH 02/13] allow no group param and improve error handling --- bindings/python3/econf/econf.py | 201 ++++++++++++-------------------- 1 file changed, 77 insertions(+), 124 deletions(-) diff --git a/bindings/python3/econf/econf.py b/bindings/python3/econf/econf.py index b49a336..6fdc98a 100644 --- a/bindings/python3/econf/econf.py +++ b/bindings/python3/econf/econf.py @@ -105,28 +105,28 @@ def _encode_str(string: str | bytes) -> bytes: return string -def _check_int_overflow(val: int) -> bool: +def _ensure_valid_int(val: int) -> int: if isinstance(val, int): c_val = c_int64(val) - return c_val.value == val + if not c_val.value == val: + raise ValueError( + "Integer overflow found, only up to 64 bit signed integers are supported" + ) + return c_val else: - raise TypeError("parameter is not an integer") + raise TypeError(f"parameter {val} is not an integer") -def _check_uint_overflow(val: int) -> bool: - if isinstance(val, int) & (val >= 0): +def _ensure_valid_uint(val: int) -> int: + if isinstance(val, int) and (val >= 0): c_val = c_uint64(val) - return c_val.value == val + if not c_val.value == val: + raise ValueError( + "Integer overflow found, only up to 64 bit unsigned integers are supported" + ) + return c_vala else: - raise TypeError("parameter is not a unsigned integer") - - -def _check_float_overflow(val: float) -> bool: - if isinstance(val, float): - c_val = c_double(val) - return c_val.value == val - else: - raise TypeError("parameter is not a float") + raise TypeError(f"parameter {val} is not an unsigned integer") def set_value( @@ -153,7 +153,7 @@ def set_value( elif isinstance(value, bool): res = set_bool_value(ef, group, key, value) else: - raise TypeError("'value' parameter is not one of the supported types") + raise TypeError(f"parameter {val} is not one of the supported types") return res @@ -172,9 +172,7 @@ def read_file( file_name = _encode_str(file_name) delim = _encode_str(delim) comment = _encode_str(comment) - err = LIBECONF.econf_readFile( - byref(result._ptr), file_name, delim, comment - ) + err = LIBECONF.econf_readFile(byref(result._ptr), file_name, delim, comment) if err: raise _exceptions(err, f"read_file failed with error: {err_string(err)}") return result @@ -330,9 +328,7 @@ def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> Econf_err: """ c_save_to_dir = _encode_str(save_to_dir) c_file_name = _encode_str(file_name) - err = LIBECONF.econf_writeFile( - byref(ef._ptr), c_save_to_dir, c_file_name - ) + err = LIBECONF.econf_writeFile(byref(ef._ptr), c_save_to_dir, c_file_name) return Econf_err(err) @@ -377,9 +373,7 @@ def get_keys(ef: EconfFile, group: str) -> list[str]: c_keys = c_void_p(None) if group: group = _encode_str(group) - err = LIBECONF.econf_getKeys( - ef._ptr, group, byref(c_length), byref(c_keys) - ) + err = LIBECONF.econf_getKeys(ef._ptr, group, byref(c_length), byref(c_keys)) if err: _exceptions(err, f"get_keys failed with error: {err_string(err)}") arr = cast(c_keys, POINTER(c_char_p * c_length.value)) @@ -396,12 +390,11 @@ def get_int_value(ef: EconfFile, group: str, key: str) -> int: :param key: key of the value that is requested :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_int64() - err = LIBECONF.econf_getInt64Value( - ef._ptr, c_group, c_key, byref(c_result) - ) + err = LIBECONF.econf_getInt64Value(ef._ptr, group, c_key, byref(c_result)) if err: _exceptions(err, f"get_int64_value failed with error: {err_string(err)}") return c_result.value @@ -416,12 +409,11 @@ def get_uint_value(ef: EconfFile, group: str, key: str) -> int: :param key: key of the value that is requested :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_uint64() - err = LIBECONF.econf_getUInt64Value( - ef._ptr, c_group, c_key, byref(c_result) - ) + err = LIBECONF.econf_getUInt64Value(ef._ptr, group, c_key, byref(c_result)) if err: _exceptions(err, f"get_uint64_value failed with error: {err_string(err)}") return c_result.value @@ -436,12 +428,11 @@ def get_float_value(ef: EconfFile, group: str, key: str) -> float: :param key: key of the value that is requested :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_double() - err = LIBECONF.econf_getDoubleValue( - ef._ptr, c_group, c_key, byref(c_result) - ) + err = LIBECONF.econf_getDoubleValue(ef._ptr, group, c_key, byref(c_result)) if err: _exceptions(err, f"get_double_value failed with error: {err_string(err)}") return c_result.value @@ -456,12 +447,11 @@ def get_string_value(ef: EconfFile, group: str, key: str) -> str: :param key: key of the value that is requested :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_char_p() - err = LIBECONF.econf_getStringValue( - ef._ptr, c_group, c_key, byref(c_result) - ) + err = LIBECONF.econf_getStringValue(ef._ptr, group, c_key, byref(c_result)) if err: _exceptions(err, f"get_string_value failed with error: {err_string(err)}") return c_result.value.decode("utf-8") @@ -476,12 +466,11 @@ def get_bool_value(ef: EconfFile, group: str, key: str) -> bool: :param key: key of the value that is requested :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_bool() - err = LIBECONF.econf_getBoolValue( - ef._ptr, c_group, c_key, byref(c_result) - ) + err = LIBECONF.econf_getBoolValue(ef._ptr, group, c_key, byref(c_result)) if err: _exceptions(err, f"get_bool_value failed with error: {err_string(err)}") return c_result.value @@ -497,21 +486,16 @@ def get_int_value_def(ef: EconfFile, group: str, key: str, default: int) -> int: :param default: value to be returned if no key is found :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_int64() - if not isinstance(default, int): - raise TypeError(f'"default" parameter must be of type int') - if not _check_int_overflow(default): - raise ValueError( - f"Integer overflow found, only up to 64 bit integers are supported" - ) - c_default = c_int64(default) + c_default = _ensure_valid_int(default) err = LIBECONF.econf_getInt64ValueDef( - ef._ptr, c_group, c_key, byref(c_result), c_default + ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == 5: + if err == Econf_err(NO_KEY): return c_default.value _exceptions(err, f"get_int64_value_def failed with error: {err_string(err)}") return c_result.value @@ -527,23 +511,16 @@ def get_uint_value_def(ef: EconfFile, group: str, key: str, default: int) -> int :param default: value to be returned if no key is found :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_uint64() - if not isinstance(default, int) | (default < 0): - raise TypeError( - f'"default" parameter must be of type int and greater or equal to zero' - ) - if not _check_uint_overflow(default): - raise ValueError( - f"Integer overflow found, only up to 64 bit unsigned integers are supported" - ) - c_default = c_uint64(default) + c_default = _ensure_valid_uint(default) err = LIBECONF.econf_getUInt64ValueDef( - ef._ptr, c_group, c_key, byref(c_result), c_default + ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == 5: + if err == Econf_err(NO_KEY): return c_default.value _exceptions(err, f"get_uint64_value_def failed with error: {err_string(err)}") return c_result.value @@ -559,21 +536,18 @@ def get_float_value_def(ef: EconfFile, group: str, key: str, default: float) -> :param default: value to be returned if no key is found :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_double() if not isinstance(default, float): - raise TypeError(f'"default" parameter must be of type float') - if not _check_float_overflow(default): - raise ValueError( - f"Float overflow found, only up to 64 bit floats are supported" - ) + raise TypeError('"default" parameter must be of type float') c_default = c_double(default) err = LIBECONF.econf_getDoubleValueDef( - ef._ptr, c_group, c_key, byref(c_result), c_default + ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == 5: + if err == Econf_err(NO_KEY): return c_default.value _exceptions(err, f"get_double_value_def failed with error: {err_string(err)}") return c_result.value @@ -589,15 +563,16 @@ def get_string_value_def(ef: EconfFile, group: str, key: str, default: str) -> s :param default: value to be returned if no key is found :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_char_p() c_default = _encode_str(default) err = LIBECONF.econf_getStringValueDef( - ef._ptr, c_group, c_key, byref(c_result), c_default + ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == 5: + if err == Econf_err(NO_KEY): return c_default.decode("utf-8") _exceptions(err, f"get_string_value_def failed with error: {err_string(err)}") return c_result.value.decode("utf-8") @@ -613,17 +588,18 @@ def get_bool_value_def(ef: EconfFile, group: str, key: str, default: bool) -> bo :param default: value to be returned if no key is found :return: value of the key """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_result = c_bool() if not isinstance(default, bool): - raise TypeError(f'"value" parameter must be of type bool') + raise TypeError('"value" parameter must be of type bool') c_default = c_bool(default) err = LIBECONF.econf_getBoolValueDef( - ef._ptr, c_group, c_key, byref(c_result), c_default + ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == 5: + if err == Econf_err(NO_KEY): return c_default.value _exceptions(err, f"get_bool_value_def failed with error: {err_string(err)}") return c_result.value @@ -639,18 +615,11 @@ def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: :param value: value to be set for given key :return: Error code """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) - if not isinstance(value, int): - raise TypeError(f'"value" parameter must be of type int') - if not _check_int_overflow(value): - raise ValueError( - f"Integer overflow found, only up to 64 bit integers are supported" - ) - c_value = c_int64(value) - err = LIBECONF.econf_setInt64Value( - byref(ef._ptr), c_group, c_key, c_value - ) + c_value = _ensure_valid_int(value) + err = LIBECONF.econf_setInt64Value(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_int64_value failed with error: {err_string(err)}") return Econf_err(err) @@ -666,20 +635,11 @@ def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err :param value: value to be set for given key :return: Error code """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) - if not isinstance(value, int) | (value < 0): - raise TypeError( - f'"value" parameter must be of type int and be greater or equal to zero' - ) - if not _check_uint_overflow(value): - raise ValueError( - f"Integer overflow found, only up to 64 bit unsigned integers are supported" - ) - c_value = c_uint64(value) - err = LIBECONF.econf_setUInt64Value( - byref(ef._ptr), c_group, c_key, c_value - ) + c_value = _ensure_valid_uint(value) + err = LIBECONF.econf_setUInt64Value(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_uint64_value failed with error: {err_string(err)}") return Econf_err(err) @@ -695,18 +655,13 @@ def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> Econf_ :param value: value to be set for given key :return: Error code """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) if not isinstance(value, float): - raise TypeError(f'"value" parameter must be of type float') - if not _check_float_overflow(value): - raise ValueError( - f"Float overflow found, only up to 64 bit floats are supported" - ) + raise TypeError('"value" parameter must be of type float') c_value = c_double(value) - err = LIBECONF.econf_setDoubleValue( - byref(ef._ptr), c_group, c_key, c_value - ) + err = LIBECONF.econf_setDoubleValue(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_double_value failed with error: {err_string(err)}") return Econf_err(err) @@ -724,12 +679,11 @@ def set_string_value( :param value: value to be set for given key :return: Error code """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) c_value = _encode_str(value) - err = LIBECONF.econf_setStringValue( - byref(ef._ptr), c_group, c_key, c_value - ) + err = LIBECONF.econf_setStringValue(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_string_value failed with error: {err_string(err)}") return Econf_err(err) @@ -745,14 +699,13 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> Econf_er :param value: value to be set for given key :return: Error code """ - c_group = _encode_str(group) + if group: + group = _encode_str(group) c_key = _encode_str(key) if not isinstance(value, bool): - raise TypeError(f'"value" parameter must be of type bool') + raise TypeError('"value" parameter must be of type bool') c_value = c_bool(value) - err = LIBECONF.econf_setBoolValue( - byref(ef._ptr), c_group, c_key, c_value - ) + err = LIBECONF.econf_setBoolValue(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") return Econf_err(err) From 26db59da0aea5492d25f496582dc5f842602cb60 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Thu, 12 Oct 2023 15:34:20 +0200 Subject: [PATCH 03/13] Do not return error codes --- bindings/python3/econf/econf.py | 68 +++++++++++++++++---------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/bindings/python3/econf/econf.py b/bindings/python3/econf/econf.py index 6fdc98a..59ea4f2 100644 --- a/bindings/python3/econf/econf.py +++ b/bindings/python3/econf/econf.py @@ -124,14 +124,14 @@ def _ensure_valid_uint(val: int) -> int: raise ValueError( "Integer overflow found, only up to 64 bit unsigned integers are supported" ) - return c_vala + return c_val else: raise TypeError(f"parameter {val} is not an unsigned integer") def set_value( ef: EconfFile, group: str | bytes, key: str | bytes, value: Any -) -> Econf_err: +) -> None: """ Dynamically set a value in a keyfile and returns a status code @@ -139,22 +139,22 @@ def set_value( :param group: group of the key to be changed :param key: key to be changed :param value: desired value - :return: Error code + :return: Nothing """ if isinstance(value, int): if value >= 0: - res = set_uint_value(ef, group, key, value) + set_uint_value(ef, group, key, value) else: - res = set_int_value(ef, group, key, value) + set_int_value(ef, group, key, value) elif isinstance(value, float): - res = set_float_value(ef, group, key, value) + set_float_value(ef, group, key, value) elif isinstance(value, str) | isinstance(value, bytes): - res = set_string_value(ef, group, key, value) + set_string_value(ef, group, key, value) elif isinstance(value, bool): - res = set_bool_value(ef, group, key, value) + set_bool_value(ef, group, key, value) else: raise TypeError(f"parameter {val} is not one of the supported types") - return res + return def read_file( @@ -317,19 +317,21 @@ def new_ini_file() -> EconfFile: return result -def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> Econf_err: +def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> None: """ Write content of a keyfile to specified location :param ef: Key-Value storage object :param save_to_dir: directory into which the file has to be written :param file_name: filename with suffix of the to be written file - :return: Error code + :return: Nothing """ c_save_to_dir = _encode_str(save_to_dir) c_file_name = _encode_str(file_name) err = LIBECONF.econf_writeFile(byref(ef._ptr), c_save_to_dir, c_file_name) - return Econf_err(err) + if err: + _exceptions(err, f"write_file failed with error: {err_string(err)}") + return def get_path(ef: EconfFile) -> str: @@ -495,7 +497,7 @@ def get_int_value_def(ef: EconfFile, group: str, key: str, default: int) -> int: ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == Econf_err(NO_KEY): + if Econf_err(err) == Econf_err.ECONF_NOKEY: return c_default.value _exceptions(err, f"get_int64_value_def failed with error: {err_string(err)}") return c_result.value @@ -520,7 +522,7 @@ def get_uint_value_def(ef: EconfFile, group: str, key: str, default: int) -> int ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == Econf_err(NO_KEY): + if Econf_err(err) == Econf_err.ECONF_NOKEY: return c_default.value _exceptions(err, f"get_uint64_value_def failed with error: {err_string(err)}") return c_result.value @@ -547,7 +549,7 @@ def get_float_value_def(ef: EconfFile, group: str, key: str, default: float) -> ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == Econf_err(NO_KEY): + if Econf_err(err) == Econf_err.ECONF_NOKEY: return c_default.value _exceptions(err, f"get_double_value_def failed with error: {err_string(err)}") return c_result.value @@ -572,7 +574,7 @@ def get_string_value_def(ef: EconfFile, group: str, key: str, default: str) -> s ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == Econf_err(NO_KEY): + if Econf_err(err) == Econf_err.ECONF_NOKEY: return c_default.decode("utf-8") _exceptions(err, f"get_string_value_def failed with error: {err_string(err)}") return c_result.value.decode("utf-8") @@ -599,13 +601,13 @@ def get_bool_value_def(ef: EconfFile, group: str, key: str, default: bool) -> bo ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if err == Econf_err(NO_KEY): + if Econf_err(err) == Econf_err.ECONF_NOKEY: return c_default.value _exceptions(err, f"get_bool_value_def failed with error: {err_string(err)}") return c_result.value -def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: +def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> None: """ Setting an integer value for given group/key @@ -613,7 +615,7 @@ def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: :param group: desired group :param key: key of the value that is requested :param value: value to be set for given key - :return: Error code + :return: Nothing """ if group: group = _encode_str(group) @@ -622,10 +624,10 @@ def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: err = LIBECONF.econf_setInt64Value(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_int64_value failed with error: {err_string(err)}") - return Econf_err(err) + return -def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err: +def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None: """ Setting an unsigned integer value for given group/key @@ -633,7 +635,7 @@ def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err :param group: desired group :param key: key of the value that is requested :param value: value to be set for given key - :return: Error code + :return: Nothing """ if group: group = _encode_str(group) @@ -642,10 +644,10 @@ def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> Econf_err err = LIBECONF.econf_setUInt64Value(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_uint64_value failed with error: {err_string(err)}") - return Econf_err(err) + return -def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> Econf_err: +def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None: """ Setting a float value for given group/key @@ -653,7 +655,7 @@ def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> Econf_ :param group: desired group :param key: key of the value that is requested :param value: value to be set for given key - :return: Error code + :return: Nothing """ if group: group = _encode_str(group) @@ -664,12 +666,12 @@ def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> Econf_ err = LIBECONF.econf_setDoubleValue(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_double_value failed with error: {err_string(err)}") - return Econf_err(err) + return def set_string_value( ef: EconfFile, group: str, key: str, value: str | bytes -) -> Econf_err: +) -> None: """ Setting a string value for given group/key @@ -677,7 +679,7 @@ def set_string_value( :param group: desired group :param key: key of the value that is requested :param value: value to be set for given key - :return: Error code + :return: Nothing """ if group: group = _encode_str(group) @@ -686,10 +688,10 @@ def set_string_value( err = LIBECONF.econf_setStringValue(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_string_value failed with error: {err_string(err)}") - return Econf_err(err) + return -def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> Econf_err: +def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: """ Setting a boolean value for given group/key @@ -697,7 +699,7 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> Econf_er :param group: desired group :param key: key of the value that is requested :param value: value to be set for given key - :return: Error code + :return: Nothing """ if group: group = _encode_str(group) @@ -708,7 +710,7 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> Econf_er err = LIBECONF.econf_setBoolValue(byref(ef._ptr), group, c_key, c_value) if err: _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") - return Econf_err(err) + return def err_string(error: int): @@ -725,7 +727,7 @@ def err_string(error: int): return LIBECONF.econf_errString(error).decode("utf-8") -def err_location(): +def err_location() -> Tuple[str, int]: """ Info about the line where an error happened From 2a8995c72fffad8ca4d518e9b1d2db89bf16c49e Mon Sep 17 00:00:00 2001 From: nkrapp Date: Thu, 12 Oct 2023 18:19:27 +0200 Subject: [PATCH 04/13] Do not pass econffile by reference --- bindings/python3/econf/econf.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bindings/python3/econf/econf.py b/bindings/python3/econf/econf.py index 59ea4f2..b241ff7 100644 --- a/bindings/python3/econf/econf.py +++ b/bindings/python3/econf/econf.py @@ -621,7 +621,7 @@ def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> None: group = _encode_str(group) c_key = _encode_str(key) c_value = _ensure_valid_int(value) - err = LIBECONF.econf_setInt64Value(byref(ef._ptr), group, c_key, c_value) + err = LIBECONF.econf_setInt64Value(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_int64_value failed with error: {err_string(err)}") return @@ -641,7 +641,7 @@ def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None: group = _encode_str(group) c_key = _encode_str(key) c_value = _ensure_valid_uint(value) - err = LIBECONF.econf_setUInt64Value(byref(ef._ptr), group, c_key, c_value) + err = LIBECONF.econf_setUInt64Value(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_uint64_value failed with error: {err_string(err)}") return @@ -663,7 +663,7 @@ def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None: if not isinstance(value, float): raise TypeError('"value" parameter must be of type float') c_value = c_double(value) - err = LIBECONF.econf_setDoubleValue(byref(ef._ptr), group, c_key, c_value) + err = LIBECONF.econf_setDoubleValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_double_value failed with error: {err_string(err)}") return @@ -685,7 +685,7 @@ def set_string_value( group = _encode_str(group) c_key = _encode_str(key) c_value = _encode_str(value) - err = LIBECONF.econf_setStringValue(byref(ef._ptr), group, c_key, c_value) + err = LIBECONF.econf_setStringValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_string_value failed with error: {err_string(err)}") return @@ -707,7 +707,7 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: if not isinstance(value, bool): raise TypeError('"value" parameter must be of type bool') c_value = c_bool(value) - err = LIBECONF.econf_setBoolValue(byref(ef._ptr), group, c_key, c_value) + err = LIBECONF.econf_setBoolValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") return From d75c0a7c3509a0417f51973c1dc114cbf81eefc4 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Fri, 13 Oct 2023 10:59:58 +0200 Subject: [PATCH 05/13] fix set bool parameter type --- bindings/python3/econf/econf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python3/econf/econf.py b/bindings/python3/econf/econf.py index b241ff7..8f2d495 100644 --- a/bindings/python3/econf/econf.py +++ b/bindings/python3/econf/econf.py @@ -706,7 +706,7 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: c_key = _encode_str(key) if not isinstance(value, bool): raise TypeError('"value" parameter must be of type bool') - c_value = c_bool(value) + c_value = _encode_str(str(value)) err = LIBECONF.econf_setBoolValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") From 9c888dc797fdf5833dbb0c80e93ab009c7b05c76 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Wed, 18 Oct 2023 13:05:09 +0200 Subject: [PATCH 06/13] Add some missing functions and refactoring --- bindings/python3/{econf => }/econf.py | 140 +++++++++++++++++++------- bindings/python3/econf/__init__.py | 3 - bindings/python3/pyproject.toml | 7 +- 3 files changed, 107 insertions(+), 43 deletions(-) rename bindings/python3/{econf => }/econf.py (84%) delete mode 100644 bindings/python3/econf/__init__.py diff --git a/bindings/python3/econf/econf.py b/bindings/python3/econf.py similarity index 84% rename from bindings/python3/econf/econf.py rename to bindings/python3/econf.py index 8f2d495..84b6fb5 100644 --- a/bindings/python3/econf/econf.py +++ b/bindings/python3/econf.py @@ -51,47 +51,47 @@ class Econf_err(Enum): def _exceptions(err: int, val: str): - if err == 1: + if Econf_err(err) == Econf_err.ECONF_ERROR: raise Exception(val) - elif err == 2: + elif Econf_err(err) == Econf_err.ECONF_NOMEM: raise MemoryError(val) - elif err == 3: + elif Econf_err(err) == Econf_err.ECONF_NOFILE: raise FileNotFoundError(val) - elif err == 4: + elif Econf_err(err) == Econf_err.ECONF_NOGROUP: raise KeyError(val) - elif err == 5: + elif Econf_err(err) == Econf_err.ECONF_NOKEY: raise KeyError(val) - elif err == 6: + elif Econf_err(err) == Econf_err.ECONF_EMPTYKEY: raise KeyError(val) - elif err == 7: + elif Econf_err(err) == Econf_err.ECONF_WRITEERROR: raise OSError(val) - elif err == 8: + elif Econf_err(err) == Econf_err.ECONF_PARSE_ERROR: raise Exception(val) - elif err == 9: + elif Econf_err(err) == Econf_err.ECONF_MISSING_BRACKET: raise SyntaxError(val) - elif err == 10: + elif Econf_err(err) == Econf_err.ECONF_MISSING_DELIMITER: raise SyntaxError(val) - elif err == 11: + elif Econf_err(err) == Econf_err.ECONF_EMPTY_SECTION_NAME: raise SyntaxError(val) - elif err == 12: + elif Econf_err(err) == Econf_err.ECONF_TEXT_AFTER_SECTION: raise SyntaxError(val) - elif err == 13: + elif Econf_err(err) == Econf_err.ECONF_FILE_LIST_IS_NULL: raise ValueError(val) - elif err == 14: + elif Econf_err(err) == Econf_err.ECONF_WRONG_BOOLEAN_VALUE: raise ValueError(val) - elif err == 15: + elif Econf_err(err) == Econf_err.ECONF_KEY_HAS_NULL_VALUE: raise ValueError(val) - elif err == 16: + elif Econf_err(err) == Econf_err.ECONF_WRONG_OWNER: raise PermissionError(val) - elif err == 17: + elif Econf_err(err) == Econf_err.ECONF_WRONG_GROUP: raise PermissionError(val) - elif err == 18: + elif Econf_err(err) == Econf_err.ECONF_WRONG_FILE_PERMISSION: raise PermissionError(val) - elif err == 19: + elif Econf_err(err) == Econf_err.ECONF_WRONG_DIR_PERMISSION: raise PermissionError(val) - elif err == 20: + elif Econf_err(err) == Econf_err.ECONF_ERROR_FILE_IS_SYM_LINK: raise FileNotFoundError(val) - elif err == 21: + elif Econf_err(err) == Econf_err.ECONF_PARSING_CALLBACK_FAILED: raise Exception(val) else: raise Exception(val) @@ -130,7 +130,7 @@ def _ensure_valid_uint(val: int) -> int: def set_value( - ef: EconfFile, group: str | bytes, key: str | bytes, value: Any + ef: EconfFile, group: str | bytes, key: str | bytes, value: int | float | str | bool ) -> None: """ Dynamically set a value in a keyfile and returns a status code @@ -154,7 +154,6 @@ def set_value( set_bool_value(ef, group, key, value) else: raise TypeError(f"parameter {val} is not one of the supported types") - return def read_file( @@ -197,7 +196,6 @@ def merge_files(usr_file: EconfFile, etc_file: EconfFile) -> EconfFile: return merged_file -# this reads either the first OR the second file if the first one does not exist def read_dirs( usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, @@ -239,7 +237,6 @@ def read_dirs( return result -# this reads either the first OR the second file if the first one does not exist def read_dirs_history( usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, @@ -317,6 +314,60 @@ def new_ini_file() -> EconfFile: return result +def comment_tag(ef: EconfFile) -> str: + """ + Get the comment tag of the specified EconfFile + + :param ef: Key-Value storage object + :return: The comment tag of the EconfFile + """ + LIBECONF.econf_comment_tag.restype = c_char + result = LIBECONF.econf_comment_tag(ef._ptr) + return result.decode("utf-8") + + +def delimiter_tag(ef: EconfFile) -> str: + """ + Get the delimiter tag of the specified EconfFile + + :param ef: Key-Value storage object + :return: the delimiter tag of the EconfFile + """ + LIBECONF.econf_delimiter_tag.restype = c_char + result = LIBECONF.econf_delimiter_tag(ef._ptr) + return result.decode("utf-8") + + +def set_comment_tag(ef: EconfFile, comment: str | bytes) -> None: + """ + Set the comment tag of the specified EconfFile + + :param ef: Key-Value storage object + :param comment: The desired comment tag character + :return: Nothing + """ + comment = _encode_str(comment) + if len(comment) > 1: + raise ValueError("Only single characters are allowed") + c_comment = c_char(comment) + LIBECONF.econf_set_comment_tag(ef._ptr, c_comment) + + +def set_delimiter_tag(ef: EconfFile, delimiter: str | bytes) -> None: + """ + Set the delimiter tag of the specified EconfFile + + :param ef: Key-Value storage object + :param delimiter: The desired delimiter character + :return: Nothing + """ + delimiter = _encode_str(delimiter) + if len(delimiter) > 1: + raise ValueError("Only single characters are allowed") + c_delimiter = c_char(delimiter) + LIBECONF.econf_set_delimiter_tag(ef._ptr, c_delimiter) + + def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> None: """ Write content of a keyfile to specified location @@ -331,7 +382,6 @@ def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> None: err = LIBECONF.econf_writeFile(byref(ef._ptr), c_save_to_dir, c_file_name) if err: _exceptions(err, f"write_file failed with error: {err_string(err)}") - return def get_path(ef: EconfFile) -> str: @@ -624,7 +674,6 @@ def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> None: err = LIBECONF.econf_setInt64Value(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_int64_value failed with error: {err_string(err)}") - return def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None: @@ -644,7 +693,6 @@ def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None: err = LIBECONF.econf_setUInt64Value(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_uint64_value failed with error: {err_string(err)}") - return def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None: @@ -666,12 +714,9 @@ def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None: err = LIBECONF.econf_setDoubleValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_double_value failed with error: {err_string(err)}") - return -def set_string_value( - ef: EconfFile, group: str, key: str, value: str | bytes -) -> None: +def set_string_value(ef: EconfFile, group: str, key: str, value: str | bytes) -> None: """ Setting a string value for given group/key @@ -688,7 +733,6 @@ def set_string_value( err = LIBECONF.econf_setStringValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_string_value failed with error: {err_string(err)}") - return def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: @@ -710,10 +754,9 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: err = LIBECONF.econf_setBoolValue(ef._ptr, group, c_key, c_value) if err: _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") - return -def err_string(error: int): +def err_string(error: int) -> str: """ Convert an error code into error message @@ -746,5 +789,30 @@ def free_file(ef: EconfFile): :param ef: EconfFile to be freed :return: None """ + if not ef._ptr: + return LIBECONF.econf_freeFile(ef._ptr) - return + + +def set_conf_dirs(dir_postfix_list: list[str]) -> None: + """ + Set a list of directories (with order) that describe the paths where files have to be parsed + + E.G. with the given list: {"/conf.d/", ".d/", "/", NULL} files in following directories will be parsed: + "/..d/" "//conf.d/" "/.d/" "//" + The entry "/..d/" will be added automatically. + + :param dir_postfix_list: List of directories + :return: None + """ + if type(dir_postfix_list) != list: + raise TypeError("Directories must be passed as a list of strings") + if len(dir_postfix_list) == 0: + return + str_arr = c_char_p * len(dir_postfix_list) + dir_arr = str_arr() + for i in range(len(dir_postfix_list)): + dir_arr[i] = c_char_p(_encode_str(dir_postfix_list[i])) + err = LIBECONF.econf_set_conf_dirs(dir_arr) + if err: + _exceptions(err, f"set_conf_dirs failed with error: {err_string(err)}") diff --git a/bindings/python3/econf/__init__.py b/bindings/python3/econf/__init__.py deleted file mode 100644 index 5c66dfb..0000000 --- a/bindings/python3/econf/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from econf.econf import * -from econf.econf import EconfFile -from econf.econf import _encode_str, _check_int_overflow, _check_uint_overflow, _check_float_overflow diff --git a/bindings/python3/pyproject.toml b/bindings/python3/pyproject.toml index 47883bd..18d0a3f 100644 --- a/bindings/python3/pyproject.toml +++ b/bindings/python3/pyproject.toml @@ -7,13 +7,12 @@ name = "python-libeconf" version = "1.0.0" description = "Python bindings for libeconf" authors = [{name="nkrapp", email="nico.krapp@suse.com"}] -readme = "README.md" [project.optional-dependencies] doc_requires = [ "sphinx", "sphinx-rtd-theme" ] - -[tool.isort] -profile = "black" +test_requires = [ + "pytest" +] From f3165089b7288aa197106fe778e5161f19062b57 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Wed, 18 Oct 2023 13:05:50 +0200 Subject: [PATCH 07/13] Add man pages for documentation --- bindings/python3/docs/python-libeconf.1 | 652 ++++++++++++++++++++++++ 1 file changed, 652 insertions(+) create mode 100644 bindings/python3/docs/python-libeconf.1 diff --git a/bindings/python3/docs/python-libeconf.1 b/bindings/python3/docs/python-libeconf.1 new file mode 100644 index 0000000..37b2631 --- /dev/null +++ b/bindings/python3/docs/python-libeconf.1 @@ -0,0 +1,652 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "PYTHON-LIBECONF" "1" "Oct 18, 2023" "" "python-libeconf" +.SH NAME +python-libeconf \- python-libeconf 1.0.0 +.sp +\fBpython\-libeconf\fP is a Python Library which offers Python bindings for +\fI\%libeconf\fP\&. +.sp +libeconf is a highly flexible and configurable library to parse and manage key=value configuration files. +It reads configuration file snippets from different directories and builds the final configuration file for +the application from it. +.sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +This project is still under development +.UNINDENT +.UNINDENT +.SH CONTENTS +.SS econf +.sp +Econf provides functionality for interacting with Key\-Value config files, like getting and setting values for read config files. +.sp +For more information please have a look at the API +.SS Usage +.SS install +.sp +\fBWARNING:\fP +.INDENT 0.0 +.INDENT 3.5 +Right now we do not provide libeconf included in the project. To use this project please make sure you have libeconf installed on your system! +.UNINDENT +.UNINDENT +.sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +The package is not yet published on pypi. This is coming soon. +.UNINDENT +.UNINDENT +.sp +You can install this project from pypi with +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +pip install python\-libeconf +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +and then import it into your python project with +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +import econf +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +For information about the functions provided by this library, have a look at \fI\%API\fP +.SS API +.TS +center; +||. +_ +.TE +.SS Functions to interact with config files +.INDENT 0.0 +.TP +.B econf.read_file(file_name: str | bytes, delim: str | bytes, comment: str | bytes) -> EconfFile +Read a config file and write the key\-value pairs into a keyfile object +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBfile_name\fP \-\- absolute path of file to be parsed +.IP \(bu 2 +\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +.IP \(bu 2 +\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +.UNINDENT +.TP +.B Returns +Key\-Value storage object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.new_key_file(delim: str | bytes, comment: str | bytes) -> EconfFile +Create a new empty keyfile +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +.IP \(bu 2 +\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +.UNINDENT +.TP +.B Returns +created EconfFile object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.new_ini_file() -> EconfFile +Create a new empty keyfile with delimiter \(aq=\(aq and comment \(aq#\(aq +.INDENT 7.0 +.TP +.B Returns +created EconfFile object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.merge_files(usr_file: EconfFile, etc_file: EconfFile) -> EconfFile +Merge the content of 2 keyfile objects +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBusr_file\fP \-\- first EconfFile object +.IP \(bu 2 +\fBetc_file\fP \-\- second EconfFile object +.UNINDENT +.TP +.B Returns +merged EconfFile object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.read_dirs(usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, project_name: str | bytes, config_suffix: str | bytes, delim: str | bytes, comment: str | bytes) -> EconfFile +Read configuration from the first found config file and merge with snippets from conf.d/ directory +.sp +e.g. searches /usr/etc/ and /etc/ for an example.conf file and merges it with the snippets in either +/usr/etc/example.conf.d/ or /etc/example.conf.d +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBusr_conf_dir\fP \-\- absolute path of the first directory to be searched +.IP \(bu 2 +\fBetc_conf_dir\fP \-\- absolute path of the second directory to be searched +.IP \(bu 2 +\fBproject_name\fP \-\- basename of the configuration file +.IP \(bu 2 +\fBconfig_suffix\fP \-\- suffix of the configuration file +.IP \(bu 2 +\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +.IP \(bu 2 +\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +.UNINDENT +.TP +.B Returns +merged EconfFile object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.read_dirs_history(usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, project_name: str | bytes, config_suffix: str | bytes, delim: str | bytes, comment: str | bytes) -> list[EconfFile] +Read configuration from the first found config file and snippets from conf.d/ directory +.sp +e.g. searches /usr/etc/ and /etc/ for an example.conf file and the snippets in either +/usr/etc/example.conf.d/ or /etc/example.conf.d +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBusr_conf_dir\fP \-\- absolute path of the first directory to be searched +.IP \(bu 2 +\fBetc_conf_dir\fP \-\- absolute path of the second directory to be searched +.IP \(bu 2 +\fBproject_name\fP \-\- basename of the configuration file +.IP \(bu 2 +\fBconfig_suffix\fP \-\- suffix of the configuration file +.IP \(bu 2 +\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +.IP \(bu 2 +\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +.UNINDENT +.TP +.B Returns +list of EconfFile objects +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> None +Write content of a keyfile to specified location +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBsave_to_dir\fP \-\- directory into which the file has to be written +.IP \(bu 2 +\fBfile_name\fP \-\- filename with suffix of the to be written file +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.SS Functions for getting values +.INDENT 0.0 +.TP +.B econf.get_groups(ef: EconfFile) -> list[str] +List all the groups of given keyfile +.INDENT 7.0 +.TP +.B Parameters +\fBef\fP \-\- Key\-Value storage object +.TP +.B Returns +list of groups in the keyfile +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_keys(ef: EconfFile, group: str) -> list[str] +List all the keys of a given group or all keys in a keyfile +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- group of the keys to be returned or None for keys without a group +.UNINDENT +.TP +.B Returns +list of keys in the given group +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_int_value(ef: EconfFile, group: str, key: str) -> int +Return an integer value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_uint_value(ef: EconfFile, group: str, key: str) -> int +Return an unsigned integer value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_float_value(ef: EconfFile, group: str, key: str) -> float +Return a float value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_string_value(ef: EconfFile, group: str, key: str) -> str +Return a string value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_bool_value(ef: EconfFile, group: str, key: str) -> bool +Return a boolean value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.SS Functions for getting values with defaults +.INDENT 0.0 +.TP +.B econf.get_int_value_def(ef: EconfFile, group: str, key: str, default: int) -> int +Return an integer value for given group/key or return a default value if key is not found +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBdefault\fP \-\- value to be returned if no key is found +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_uint_value_def(ef: EconfFile, group: str, key: str, default: int) -> int +Return an unsigned integer value for given group/key or return a default value if key is not found +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBdefault\fP \-\- value to be returned if no key is found +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_float_value_def(ef: EconfFile, group: str, key: str, default: float) -> float +Return a float value for given group/key or return a default value if key is not found +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBdefault\fP \-\- value to be returned if no key is found +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_string_value_def(ef: EconfFile, group: str, key: str, default: str) -> str +Return a string value for given group/key or return a default value if key is not found +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBdefault\fP \-\- value to be returned if no key is found +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.get_bool_value_def(ef: EconfFile, group: str, key: str, default: bool) -> bool +Return a boolean value for given group/key or return a default value if key is not found +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBdefault\fP \-\- value to be returned if no key is found +.UNINDENT +.TP +.B Returns +value of the key +.UNINDENT +.UNINDENT +.SS Functions for setting values +.INDENT 0.0 +.TP +.B econf.set_int_value(ef: EconfFile, group: str, key: str, value: int) -> None +Setting an integer value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBvalue\fP \-\- value to be set for given key +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None +Setting an unsigned integer value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBvalue\fP \-\- value to be set for given key +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None +Setting a float value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBvalue\fP \-\- value to be set for given key +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.set_string_value(ef: EconfFile, group: str, key: str, value: str | bytes) -> None +Setting a string value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBvalue\fP \-\- value to be set for given key +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None +Setting a boolean value for given group/key +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP \-\- Key\-Value storage object +.IP \(bu 2 +\fBgroup\fP \-\- desired group +.IP \(bu 2 +\fBkey\fP \-\- key of the value that is requested +.IP \(bu 2 +\fBvalue\fP \-\- value to be set for given key +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.SS Functions for memory management +.INDENT 0.0 +.TP +.B econf.free_file(ef: EconfFile) +Free the memory of a given keyfile +.INDENT 7.0 +.TP +.B Parameters +\fBef\fP \-\- EconfFile to be freed +.TP +.B Returns +None +.UNINDENT +.UNINDENT +.SS Functions for handling error codes +.INDENT 0.0 +.TP +.B econf.err_string(error: int) +Convert an error code into error message +.INDENT 7.0 +.TP +.B Parameters +\fBerror\fP \-\- error code as integer +.TP +.B Returns +error string +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.err_location() -> Tuple[str, int] +Info about the line where an error happened +.INDENT 7.0 +.TP +.B Returns +path to the last handled file and number of last handled line +.UNINDENT +.UNINDENT +.SH INDICES AND TABLES +.INDENT 0.0 +.IP \(bu 2 +\fI\%Index\fP +.IP \(bu 2 +\fI\%Module Index\fP +.IP \(bu 2 +\fI\%Search Page\fP +.UNINDENT +.SH AUTHOR +Nico Krapp +.SH COPYRIGHT +2023, Nico Krapp +.\" Generated by docutils manpage writer. +. From 4d939228ca6af78e8c6472e2323e49080409743a Mon Sep 17 00:00:00 2001 From: nkrapp Date: Wed, 18 Oct 2023 13:06:37 +0200 Subject: [PATCH 08/13] Add tests and workflow to run them automatically --- .github/workflows/python-tests.yml | 30 +++ bindings/python3/test/test_econf.py | 194 ++++++++++++++++++ bindings/python3/test/test_econf_getters.py | 147 +++++++++++++ .../python3/test/test_econf_getters_def.py | 128 ++++++++++++ bindings/python3/test/test_econf_setters.py | 116 +++++++++++ .../test/testdata/examples/encoding_test.conf | 1 + .../test/testdata/examples/example.conf | 20 ++ .../examples/example.conf.d/snippet.conf | 5 + .../test/testdata/examples/invalid.conf | 2 + .../test/testdata/examples2/example.conf | 2 + 10 files changed, 645 insertions(+) create mode 100644 .github/workflows/python-tests.yml create mode 100644 bindings/python3/test/test_econf.py create mode 100644 bindings/python3/test/test_econf_getters.py create mode 100644 bindings/python3/test/test_econf_getters_def.py create mode 100644 bindings/python3/test/test_econf_setters.py create mode 100644 bindings/python3/test/testdata/examples/encoding_test.conf create mode 100644 bindings/python3/test/testdata/examples/example.conf create mode 100644 bindings/python3/test/testdata/examples/example.conf.d/snippet.conf create mode 100644 bindings/python3/test/testdata/examples/invalid.conf create mode 100644 bindings/python3/test/testdata/examples2/example.conf diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml new file mode 100644 index 0000000..a6eac0f --- /dev/null +++ b/.github/workflows/python-tests.yml @@ -0,0 +1,30 @@ +name: Execute tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + paths: + - bindings/python3/* + +jobs: + test: + runs-on: ubuntu-latest + container: registry.opensuse.org/opensuse/tumbleweed:latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup libeconf + run: | + zypper ref + zypper --non-interactive in libeconf0 python3 python3-pip python3-pytest + + - name: Install econf + run: python3 -m pip install -e . --break-system-packages + + - name: Run tests + run: pytest -v test/ \ No newline at end of file diff --git a/bindings/python3/test/test_econf.py b/bindings/python3/test/test_econf.py new file mode 100644 index 0000000..a0504aa --- /dev/null +++ b/bindings/python3/test/test_econf.py @@ -0,0 +1,194 @@ +import pytest +import econf +from contextlib import contextmanager +from pathlib import Path +from ctypes import * + + +FILE = econf.read_file("test/testdata/examples/example.conf", "=", ";") +FILE2 = econf.read_file("test/testdata/examples2/example.conf", "=", "#") +INVALID_FILE = econf.read_file("test/testdata/examples/invalid.conf", ":", "#") + + +@contextmanager +def does_not_raise(): + yield + + +@pytest.mark.parametrize( + "value,expected,context", + [ + ("foo", b"foo", does_not_raise()), + (b"foo", b"foo", does_not_raise()), + (5, b"", pytest.raises(TypeError)), + ], +) +def test_encode_str(value, expected, context): + with context: + assert econf._encode_str(value) == expected + + +@pytest.mark.parametrize( + "value,context", + [ + (5, does_not_raise()), + (99999999999999999999, pytest.raises(ValueError)), + ("a", pytest.raises(TypeError)), + ], +) +def test_ensure_valid_int(value, context): + with context: + result = econf._ensure_valid_int(value) + + assert isinstance(result, c_int64) + assert result.value == value + + +@pytest.mark.parametrize( + "value,context", + [ + (5, does_not_raise()), + (99999999999999999999, pytest.raises(ValueError)), + ("a", pytest.raises(TypeError)), + ], +) +def test_ensure_valid_uint(value, context): + with context: + result = econf._ensure_valid_uint(value) + + assert isinstance(result, c_uint64) + assert result.value >= 0 + assert result.value == value + + +def test_read_file(): + file = "test/testdata/examples/example.conf" + delim = "=" + comment = "#" + + ef = econf.read_file(file, delim, comment) + + assert ef._ptr != None + + +def test_new_key_file(): + result = econf.new_key_file("=", "#") + + assert result + assert type(result) == econf.EconfFile + + +def test_new_ini_file(): + result = econf.new_ini_file() + + assert result + assert type(result) == econf.EconfFile + + +def test_merge_files(): + result = econf.merge_files(FILE, FILE2) + + assert len(econf.get_keys(result, None)) == 4 + assert len(econf.get_keys(result, "Group")) == 3 + assert len(econf.get_groups(result)) == 3 + + +def test_read_dirs(): + result = econf.read_dirs( + "test/testdata/examples2/", + "test/testdata/examples/", + "example", + "conf", + "=", + "#", + ) + + assert len(econf.get_keys(result, None)) == 3 + assert len(econf.get_keys(result, "Group")) == 4 + assert len(econf.get_groups(result)) == 3 + + +def test_read_dirs_history(): + result = econf.read_dirs_history( + "test/testdata/examples2/", + "test/testdata/examples/", + "example", + "conf", + "=", + "#", + ) + + assert len(result) == 2 + assert len(econf.get_groups(result[0])) == 3 + assert len(econf.get_keys(result[0], None)) == 2 + assert len(econf.get_groups(result[1])) == 1 + + +@pytest.mark.parametrize( + "ef,context,expected", + [ + (FILE, does_not_raise(), ";"), + (FILE2, does_not_raise(), "#"), + (INVALID_FILE, does_not_raise(), "#"), + ], +) +def test_comment_tag(ef, context, expected): + with context: + result = econf.comment_tag(ef) + + assert result == expected + + +@pytest.mark.parametrize( + "ef,context,expected", + [ + (FILE, does_not_raise(), "="), + (FILE2, does_not_raise(), "="), + (INVALID_FILE, does_not_raise(), ":"), + ], +) +def test_delimiter_tag(ef, context, expected): + with context: + result = econf.delimiter_tag(ef) + + assert result == expected + + +@pytest.mark.parametrize( + "ef,context,expected", + [ + (FILE, does_not_raise(), "/"), + (FILE, pytest.raises(TypeError), 1), + (FILE, pytest.raises(ValueError), "abc"), + ], +) +def test_set_comment_tag(ef, context, expected): + with context: + econf.set_comment_tag(ef, expected) + result = econf.comment_tag(ef) + + assert result == expected + + +@pytest.mark.parametrize( + "ef,context, expected", + [ + (FILE, does_not_raise(), ":"), + (FILE, pytest.raises(TypeError), 1), + (FILE, pytest.raises(ValueError), "abc"), + ], +) +def test_set_delimiter_tag(ef, context, expected): + with context: + econf.set_delimiter_tag(ef, expected) + result = econf.delimiter_tag(ef) + + assert result == expected + + +def test_write_file(tmp_path): + d = str(tmp_path) + name = "example.conf" + result = econf.write_file(FILE, d, name) + + assert (tmp_path / "example.conf").exists() diff --git a/bindings/python3/test/test_econf_getters.py b/bindings/python3/test/test_econf_getters.py new file mode 100644 index 0000000..9548c34 --- /dev/null +++ b/bindings/python3/test/test_econf_getters.py @@ -0,0 +1,147 @@ +import pytest +import econf +from contextlib import contextmanager +from pathlib import Path +from ctypes import * + + +FILE = econf.read_file("test/testdata/examples/example.conf", "=", ";") +FILE2 = econf.read_file("test/testdata/examples2/example.conf", "=", "#") + + +@contextmanager +def does_not_raise(): + yield + + +@pytest.mark.parametrize( + "file,context,example", + [ + (FILE, does_not_raise(), ["Another Group", "First Group", "Group"]), + (FILE2, pytest.raises(KeyError), []), + ], +) +def test_get_groups(file, context, example): + with context: + assert econf.get_groups(file) == example + + +@pytest.mark.parametrize( + "file,context,group,expected", + [ + (FILE, does_not_raise(), "Group", 3), + (FILE, does_not_raise(), None, 2), + (FILE2, does_not_raise(), None, 2), + (FILE, pytest.raises(TypeError), 1, 0), + (FILE, pytest.raises(KeyError), "a", 0), + ], +) +def test_get_keys(file, context, group, expected): + with context: + result = econf.get_keys(file, group) + + assert len(result) == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Bla", 12311), + (FILE, pytest.raises(KeyError), "Group", "a", 0), + (FILE, pytest.raises(KeyError), "a", "Bla", 12311), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, does_not_raise(), None, "foo", 6), + (FILE, does_not_raise(), "Group", "Welcome", 0), + ], +) +def test_get_int_value(file, context, group, key, expected): + with context: + result = econf.get_int_value(file, group, key) + + assert isinstance(result, int) + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Bla", 12311), + (FILE, pytest.raises(KeyError), "Group", "a", 0), + (FILE, pytest.raises(KeyError), "a", "Bla", 12311), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, does_not_raise(), None, "foo", 6), + (FILE, does_not_raise(), "Group", "Welcome", 0), + ], +) +def test_get_uint_value(file, context, group, key, expected): + with context: + result = econf.get_uint_value(file, group, key) + + assert isinstance(result, int) + assert result >= 0 + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Bla", 12311), + (FILE, pytest.raises(KeyError), "Group", "a", 0), + (FILE, pytest.raises(KeyError), "a", "Bla", 12311), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, does_not_raise(), None, "foo", 6.5), + (FILE, does_not_raise(), "Group", "Welcome", 0), + ], +) +def test_get_float_value(file, context, group, key, expected): + with context: + result = econf.get_float_value(file, group, key) + + assert isinstance(result, float) + assert result >= 0 + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Welcome", "Hello"), + ( + FILE, + does_not_raise(), + "First Group", + "Name", + "Keys File Example\\tthis value shows\\nescaping", + ), + (FILE, does_not_raise(), "First Group", "Welcome[de]", "Hallo"), + (FILE, does_not_raise(), "Group", "Bla", "12311"), + (FILE, does_not_raise(), None, "foo", "6.5"), + (FILE, pytest.raises(KeyError), "a", "Bla", "12311"), + (FILE, pytest.raises(KeyError), "Group", "foo", "6.5"), + (FILE, pytest.raises(TypeError), 7, 2, "12311"), + ], +) +def test_get_string_value(file, context, group, key, expected): + with context: + result = econf.get_string_value(file, group, key) + + assert isinstance(result, str) + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Another Group", "Booleans", True), + (FILE, pytest.raises(Exception, match="Parse error"), "Group", "Bla", True), + (FILE, pytest.raises(KeyError), "a", "Booleans", True), + (FILE, pytest.raises(KeyError), "Another Group", "Bools", True), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + ], +) +def test_get_bool_value(file, context, group, key, expected): + with context: + result = econf.get_bool_value(file, group, key) + + assert isinstance(result, bool) + assert result == expected diff --git a/bindings/python3/test/test_econf_getters_def.py b/bindings/python3/test/test_econf_getters_def.py new file mode 100644 index 0000000..25cf5dd --- /dev/null +++ b/bindings/python3/test/test_econf_getters_def.py @@ -0,0 +1,128 @@ +import pytest +import econf +from contextlib import contextmanager +from pathlib import Path +from ctypes import * + + +FILE = econf.read_file("test/testdata/examples/example.conf", "=", ";") +FILE2 = econf.read_file("test/testdata/examples2/example.conf", "=", "#") + + +@contextmanager +def does_not_raise(): + yield + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Bla", 12311), + (FILE, does_not_raise(), "Group", "Invalid Key", 1), + (FILE, does_not_raise(), "Invalid Group", "Bla", 1), + (FILE, does_not_raise(), None, "foo", 6), + (FILE, does_not_raise(), "Group", "Welcome", 0), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, pytest.raises(TypeError), "Group", "Bla", "default"), + (FILE, pytest.raises(TypeError), "Group", "Invalid Key", "default"), + ], +) +def test_get_int_value_def(file, context, group, key, expected): + with context: + result = econf.get_int_value_def(file, group, key, expected) + + assert isinstance(result, int) + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Bla", 12311), + (FILE, does_not_raise(), "Group", "Invalid Key", 0), + (FILE, does_not_raise(), "Invalid Group", "Bla", 12311), + (FILE, does_not_raise(), None, "foo", 6), + (FILE, does_not_raise(), "Group", "Welcome", 0), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, pytest.raises(TypeError), "Group", "Bla", "default"), + (FILE, pytest.raises(TypeError), "Group", "Invalid Key", "default"), + ], +) +def test_get_uint_value_def(file, context, group, key, expected): + with context: + result = econf.get_uint_value_def(file, group, key, expected) + + assert isinstance(result, int) + assert result >= 0 + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Bla", 12311.0), + (FILE, does_not_raise(), "Group", "Invalid Key", 0.1), + (FILE, does_not_raise(), "Invalid Group", "Bla", 12311.1), + (FILE, does_not_raise(), None, "foo", 6.5), + (FILE, does_not_raise(), "Group", "Welcome", 0.0), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, pytest.raises(TypeError), None, "foo", "default"), + (FILE, pytest.raises(TypeError), "Group", "Invalid Key", "default"), + ], +) +def test_get_float_value_def(file, context, group, key, expected): + with context: + result = econf.get_float_value_def(file, group, key, expected) + + assert isinstance(result, float) + assert result >= 0 + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Group", "Welcome", "Hello"), + ( + FILE, + does_not_raise(), + "First Group", + "Name", + "Keys File Example\\tthis value shows\\nescaping", + ), + (FILE, does_not_raise(), "First Group", "Welcome[de]", "Hallo"), + (FILE, does_not_raise(), "Group", "Bla", "12311"), + (FILE, does_not_raise(), None, "foo", "6.5"), + (FILE, does_not_raise(), "Invalid Group", "Bla", "default"), + (FILE, does_not_raise(), "Group", "foo", "default"), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, pytest.raises(TypeError), "Group", "Welcome", 7), + (FILE, pytest.raises(TypeError), "Group", "Invalid Key", 7), + ], +) +def test_get_string_value_def(file, context, group, key, expected): + with context: + result = econf.get_string_value_def(file, group, key, expected) + + assert isinstance(result, str) + assert result == expected + + +@pytest.mark.parametrize( + "file,context,group,key,expected", + [ + (FILE, does_not_raise(), "Another Group", "Booleans", True), + (FILE, pytest.raises(Exception, match="Parse error"), "Group", "Bla", True), + (FILE, does_not_raise(), "Invalid Group", "Booleans", False), + (FILE, does_not_raise(), "Another Group", "Bools", False), + (FILE, pytest.raises(TypeError), 7, 2, 12311), + (FILE, pytest.raises(TypeError), "Another Group", "Booleans", 12311), + (FILE, pytest.raises(TypeError), "Another Group", "Bools", 12311), + ], +) +def test_get_bool_value_def(file, context, group, key, expected): + with context: + result = econf.get_bool_value_def(file, group, key, expected) + + assert isinstance(result, bool) + assert result == expected diff --git a/bindings/python3/test/test_econf_setters.py b/bindings/python3/test/test_econf_setters.py new file mode 100644 index 0000000..c721df3 --- /dev/null +++ b/bindings/python3/test/test_econf_setters.py @@ -0,0 +1,116 @@ +import pytest +import econf +from contextlib import contextmanager +from pathlib import Path +from ctypes import * + + +FILE = econf.read_file("test/testdata/examples/example.conf", "=", ";") +FILE2 = econf.read_file("test/testdata/examples2/example.conf", "=", "#") + + +@contextmanager +def does_not_raise(): + yield + + +@pytest.mark.parametrize( + "file,context,group,key,value", + [ + (FILE, does_not_raise(), "Group", "Bla", 1), + (FILE, does_not_raise(), "Group", "Welcome", 1), + (FILE, does_not_raise(), None, "foo2", 1), + (FILE, pytest.raises(ValueError), "Group", "Bla", 99999999999999999999), + (FILE, does_not_raise(), "New Group", "Bla", 1), + (FILE, pytest.raises(TypeError), 7, 2, 1), + (FILE, pytest.raises(TypeError), "Group", "Bla", "Invalid Value"), + (FILE, pytest.raises(TypeError), "Invalid Group", "Bla", "Invalid Value"), + ], +) +def test_set_int_value(file, context, group, key, value): + with context: + econf.set_int_value(file, group, key, value) + result = econf.get_int_value(file, group, key) + + assert result == value + + +@pytest.mark.parametrize( + "file,context,group,key,value", + [ + (FILE, does_not_raise(), "Group", "Bla", 1), + (FILE, does_not_raise(), "Group", "Welcome", 1), + (FILE, does_not_raise(), "New Group", "Bla", 1), + (FILE, does_not_raise(), None, "foo2", 1), + (FILE, pytest.raises(TypeError), "Group", "Bla", -1), + (FILE, pytest.raises(ValueError), "Group", "Bla", 99999999999999999999), + (FILE, pytest.raises(TypeError), 7, 2, 1), + (FILE, pytest.raises(TypeError), "Group", "Bla", "Invalid Value"), + (FILE, pytest.raises(TypeError), "Invalid Group", "Bal", "Invalid Value"), + ], +) +def test_set_uint_value(file, context, group, key, value): + with context: + econf.set_uint_value(file, group, key, value) + result = econf.get_uint_value(file, group, key) + + assert result == value + + +@pytest.mark.parametrize( + "file,context,group,key,value", + [ + (FILE, does_not_raise(), None, "foo", 1.5), + (FILE, does_not_raise(), "Group", "Welcome", 1.5), + (FILE, does_not_raise(), "New Group", "Bla", 1.5), + (FILE, does_not_raise(), "Group", "Bla", -1.5), + (FILE, pytest.raises(TypeError), 7, 2, 1), + (FILE, pytest.raises(TypeError), "Group", "Bla", "Invalid Value"), + (FILE, pytest.raises(TypeError), "Group", "Bla", 1), + ], +) +def test_set_float_value(file, context, group, key, value): + with context: + econf.set_float_value(file, group, key, value) + result = econf.get_float_value(file, group, key) + + assert result == value + + +@pytest.mark.parametrize( + "file,context,group,key,value", + [ + (FILE, does_not_raise(), "Group", "Welcome", "Bye"), + (FILE, does_not_raise(), "Group", "Bla", "1"), + (FILE, does_not_raise(), "New Group", "Welcome", "Bye"), + (FILE, does_not_raise(), "First Group", "Name", "\nNoname"), + (FILE, pytest.raises(TypeError), 7, 2, 1), + (FILE, pytest.raises(TypeError), "Group", "Bla", 1.5), + (FILE, pytest.raises(TypeError), "Group", "Bla", True), + ], +) +def test_set_string_value(file, context, group, key, value): + with context: + econf.set_string_value(file, group, key, value) + result = econf.get_string_value(file, group, key) + + assert result == value + + +@pytest.mark.parametrize( + "file,context,group,key,value", + [ + (FILE, does_not_raise(), "Another Group", "Booleans", False), + (FILE, does_not_raise(), "Group", "Bla", True), + (FILE, does_not_raise(), "New Group", "Welcome", True), + (FILE, pytest.raises(TypeError), 7, 2, 1), + (FILE, pytest.raises(TypeError), "Group", "Bla", "Invalid Value"), + (FILE, pytest.raises(TypeError), "Group", "Bla", ""), + ], +) +def test_set_bool_value(file, context, group, key, value): + with context: + econf.set_bool_value(file, group, key, value) + result = econf.get_bool_value(file, group, key) + + assert result == value diff --git a/bindings/python3/test/testdata/examples/encoding_test.conf b/bindings/python3/test/testdata/examples/encoding_test.conf new file mode 100644 index 0000000..6fd6074 --- /dev/null +++ b/bindings/python3/test/testdata/examples/encoding_test.conf @@ -0,0 +1 @@ +foo=barŸ# \ No newline at end of file diff --git a/bindings/python3/test/testdata/examples/example.conf b/bindings/python3/test/testdata/examples/example.conf new file mode 100644 index 0000000..d574046 --- /dev/null +++ b/bindings/python3/test/testdata/examples/example.conf @@ -0,0 +1,20 @@ +foo=6.5 +foo2=-6 + +[Another Group] +Numbers=2;20;-200;0;232 +Booleans=true;false + +[First Group] +Name=Keys File Example\tthis value shows\nescaping +Welcome[fr_FR]=Bojour +Welcome[la]=Salve +Welcome[it] = Ci o +Welcome[be@latin]=Hello +Welcome[de]=Hallo +Welcome=Hello + +[Group] +Bla=12311 +Welcome[la]=Salve +Welcome=Hello diff --git a/bindings/python3/test/testdata/examples/example.conf.d/snippet.conf b/bindings/python3/test/testdata/examples/example.conf.d/snippet.conf new file mode 100644 index 0000000..327d73c --- /dev/null +++ b/bindings/python3/test/testdata/examples/example.conf.d/snippet.conf @@ -0,0 +1,5 @@ +abc=5 +foo=baz + +[Group] +Number=5 \ No newline at end of file diff --git a/bindings/python3/test/testdata/examples/invalid.conf b/bindings/python3/test/testdata/examples/invalid.conf new file mode 100644 index 0000000..247fa78 --- /dev/null +++ b/bindings/python3/test/testdata/examples/invalid.conf @@ -0,0 +1,2 @@ +foo +abc \ No newline at end of file diff --git a/bindings/python3/test/testdata/examples2/example.conf b/bindings/python3/test/testdata/examples2/example.conf new file mode 100644 index 0000000..edcbdde --- /dev/null +++ b/bindings/python3/test/testdata/examples2/example.conf @@ -0,0 +1,2 @@ +fooo=bar +bar=foo \ No newline at end of file From 39ccacdacc3d226ea2c73b0035642cee0ade12af Mon Sep 17 00:00:00 2001 From: nkrapp Date: Wed, 18 Oct 2023 14:14:28 +0200 Subject: [PATCH 09/13] Improve man page --- .../{python-libeconf.1 => python-libeconf.3} | 197 ++++++++---------- 1 file changed, 88 insertions(+), 109 deletions(-) rename bindings/python3/docs/{python-libeconf.1 => python-libeconf.3} (67%) diff --git a/bindings/python3/docs/python-libeconf.1 b/bindings/python3/docs/python-libeconf.3 similarity index 67% rename from bindings/python3/docs/python-libeconf.1 rename to bindings/python3/docs/python-libeconf.3 index 37b2631..5fb1e3e 100644 --- a/bindings/python3/docs/python-libeconf.1 +++ b/bindings/python3/docs/python-libeconf.3 @@ -27,9 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PYTHON-LIBECONF" "1" "Oct 18, 2023" "" "python-libeconf" -.SH NAME -python-libeconf \- python-libeconf 1.0.0 +.TH "PYTHON-LIBECONF" "3" "Oct 18, 2023" "" "python-libeconf" .sp \fBpython\-libeconf\fP is a Python Library which offers Python bindings for \fI\%libeconf\fP\&. @@ -37,26 +35,16 @@ python-libeconf \- python-libeconf 1.0.0 libeconf is a highly flexible and configurable library to parse and manage key=value configuration files. It reads configuration file snippets from different directories and builds the final configuration file for the application from it. -.sp -\fBNOTE:\fP -.INDENT 0.0 -.INDENT 3.5 -This project is still under development -.UNINDENT -.UNINDENT .SH CONTENTS -.SS econf -.sp -Econf provides functionality for interacting with Key\-Value config files, like getting and setting values for read config files. -.sp -For more information please have a look at the API .SS Usage .SS install .sp \fBWARNING:\fP .INDENT 0.0 .INDENT 3.5 -Right now we do not provide libeconf included in the project. To use this project please make sure you have libeconf installed on your system! +The recommended way to install this project is to get the rpm package. +If you install this project from pypi libeconf will not be automatically installed. +To use this project please make sure you have libeconf installed on your system! .UNINDENT .UNINDENT .sp @@ -108,11 +96,11 @@ Read a config file and write the key\-value pairs into a keyfile object .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBfile_name\fP \-\- absolute path of file to be parsed +\fBfile_name\fP – absolute path of file to be parsed .IP \(bu 2 -\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ .IP \(bu 2 -\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ .UNINDENT .TP .B Returns @@ -128,9 +116,9 @@ Create a new empty keyfile .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ .IP \(bu 2 -\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ .UNINDENT .TP .B Returns @@ -140,7 +128,7 @@ created EconfFile object .INDENT 0.0 .TP .B econf.new_ini_file() -> EconfFile -Create a new empty keyfile with delimiter \(aq=\(aq and comment \(aq#\(aq +Create a new empty keyfile with delimiter ‘=’ and comment ‘#’ .INDENT 7.0 .TP .B Returns @@ -156,9 +144,9 @@ Merge the content of 2 keyfile objects .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBusr_file\fP \-\- first EconfFile object +\fBusr_file\fP – first EconfFile object .IP \(bu 2 -\fBetc_file\fP \-\- second EconfFile object +\fBetc_file\fP – second EconfFile object .UNINDENT .TP .B Returns @@ -177,17 +165,17 @@ e.g. searches /usr/etc/ and /etc/ for an example.conf file and merges it with th .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBusr_conf_dir\fP \-\- absolute path of the first directory to be searched +\fBusr_conf_dir\fP – absolute path of the first directory to be searched .IP \(bu 2 -\fBetc_conf_dir\fP \-\- absolute path of the second directory to be searched +\fBetc_conf_dir\fP – absolute path of the second directory to be searched .IP \(bu 2 -\fBproject_name\fP \-\- basename of the configuration file +\fBproject_name\fP – basename of the configuration file .IP \(bu 2 -\fBconfig_suffix\fP \-\- suffix of the configuration file +\fBconfig_suffix\fP – suffix of the configuration file .IP \(bu 2 -\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ .IP \(bu 2 -\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ .UNINDENT .TP .B Returns @@ -206,17 +194,17 @@ e.g. searches /usr/etc/ and /etc/ for an example.conf file and the snippets in e .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBusr_conf_dir\fP \-\- absolute path of the first directory to be searched +\fBusr_conf_dir\fP – absolute path of the first directory to be searched .IP \(bu 2 -\fBetc_conf_dir\fP \-\- absolute path of the second directory to be searched +\fBetc_conf_dir\fP – absolute path of the second directory to be searched .IP \(bu 2 -\fBproject_name\fP \-\- basename of the configuration file +\fBproject_name\fP – basename of the configuration file .IP \(bu 2 -\fBconfig_suffix\fP \-\- suffix of the configuration file +\fBconfig_suffix\fP – suffix of the configuration file .IP \(bu 2 -\fBdelim\fP \-\- delimiter of a key/value e.g. \(aq=\(aq +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ .IP \(bu 2 -\fBcomment\fP \-\- string that defines the start of a comment e.g. \(aq#\(aq +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ .UNINDENT .TP .B Returns @@ -232,11 +220,11 @@ Write content of a keyfile to specified location .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBsave_to_dir\fP \-\- directory into which the file has to be written +\fBsave_to_dir\fP – directory into which the file has to be written .IP \(bu 2 -\fBfile_name\fP \-\- filename with suffix of the to be written file +\fBfile_name\fP – filename with suffix of the to be written file .UNINDENT .TP .B Returns @@ -251,7 +239,7 @@ List all the groups of given keyfile .INDENT 7.0 .TP .B Parameters -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .TP .B Returns list of groups in the keyfile @@ -266,9 +254,9 @@ List all the keys of a given group or all keys in a keyfile .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- group of the keys to be returned or None for keys without a group +\fBgroup\fP – group of the keys to be returned or None for keys without a group .UNINDENT .TP .B Returns @@ -284,11 +272,11 @@ Return an integer value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .UNINDENT .TP .B Returns @@ -304,11 +292,11 @@ Return an unsigned integer value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .UNINDENT .TP .B Returns @@ -324,11 +312,11 @@ Return a float value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .UNINDENT .TP .B Returns @@ -344,11 +332,11 @@ Return a string value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .UNINDENT .TP .B Returns @@ -364,11 +352,11 @@ Return a boolean value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .UNINDENT .TP .B Returns @@ -385,13 +373,13 @@ Return an integer value for given group/key or return a default value if key is .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBdefault\fP \-\- value to be returned if no key is found +\fBdefault\fP – value to be returned if no key is found .UNINDENT .TP .B Returns @@ -407,13 +395,13 @@ Return an unsigned integer value for given group/key or return a default value i .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBdefault\fP \-\- value to be returned if no key is found +\fBdefault\fP – value to be returned if no key is found .UNINDENT .TP .B Returns @@ -429,13 +417,13 @@ Return a float value for given group/key or return a default value if key is not .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBdefault\fP \-\- value to be returned if no key is found +\fBdefault\fP – value to be returned if no key is found .UNINDENT .TP .B Returns @@ -451,13 +439,13 @@ Return a string value for given group/key or return a default value if key is no .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBdefault\fP \-\- value to be returned if no key is found +\fBdefault\fP – value to be returned if no key is found .UNINDENT .TP .B Returns @@ -473,13 +461,13 @@ Return a boolean value for given group/key or return a default value if key is n .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBdefault\fP \-\- value to be returned if no key is found +\fBdefault\fP – value to be returned if no key is found .UNINDENT .TP .B Returns @@ -496,13 +484,13 @@ Setting an integer value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBvalue\fP \-\- value to be set for given key +\fBvalue\fP – value to be set for given key .UNINDENT .TP .B Returns @@ -518,13 +506,13 @@ Setting an unsigned integer value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBvalue\fP \-\- value to be set for given key +\fBvalue\fP – value to be set for given key .UNINDENT .TP .B Returns @@ -540,13 +528,13 @@ Setting a float value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBvalue\fP \-\- value to be set for given key +\fBvalue\fP – value to be set for given key .UNINDENT .TP .B Returns @@ -562,13 +550,13 @@ Setting a string value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBvalue\fP \-\- value to be set for given key +\fBvalue\fP – value to be set for given key .UNINDENT .TP .B Returns @@ -584,13 +572,13 @@ Setting a boolean value for given group/key .B Parameters .INDENT 7.0 .IP \(bu 2 -\fBef\fP \-\- Key\-Value storage object +\fBef\fP – Key\-Value storage object .IP \(bu 2 -\fBgroup\fP \-\- desired group +\fBgroup\fP – desired group .IP \(bu 2 -\fBkey\fP \-\- key of the value that is requested +\fBkey\fP – key of the value that is requested .IP \(bu 2 -\fBvalue\fP \-\- value to be set for given key +\fBvalue\fP – value to be set for given key .UNINDENT .TP .B Returns @@ -605,7 +593,7 @@ Free the memory of a given keyfile .INDENT 7.0 .TP .B Parameters -\fBef\fP \-\- EconfFile to be freed +\fBef\fP – EconfFile to be freed .TP .B Returns None @@ -614,12 +602,12 @@ None .SS Functions for handling error codes .INDENT 0.0 .TP -.B econf.err_string(error: int) +.B econf.err_string(error: int) -> str Convert an error code into error message .INDENT 7.0 .TP .B Parameters -\fBerror\fP \-\- error code as integer +\fBerror\fP – error code as integer .TP .B Returns error string @@ -635,15 +623,6 @@ Info about the line where an error happened path to the last handled file and number of last handled line .UNINDENT .UNINDENT -.SH INDICES AND TABLES -.INDENT 0.0 -.IP \(bu 2 -\fI\%Index\fP -.IP \(bu 2 -\fI\%Module Index\fP -.IP \(bu 2 -\fI\%Search Page\fP -.UNINDENT .SH AUTHOR Nico Krapp .SH COPYRIGHT From 293e25bd065b62df6a09c5865a49f41c2aca4628 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Thu, 19 Oct 2023 17:02:19 +0200 Subject: [PATCH 10/13] Add Callback functions --- bindings/python3/econf.py | 246 +++++++++++++++--- bindings/python3/test/test_econf.py | 121 ++++++++- .../test/testdata/examples/invalid.conf | 6 +- 3 files changed, 323 insertions(+), 50 deletions(-) diff --git a/bindings/python3/econf.py b/bindings/python3/econf.py index 84b6fb5..f6530a6 100644 --- a/bindings/python3/econf.py +++ b/bindings/python3/econf.py @@ -105,6 +105,13 @@ def _encode_str(string: str | bytes) -> bytes: return string +def _ensure_valid_char(char: str | bytes) -> bytes: + char = _encode_str(char) + if len(char) > 1: + raise ValueError("Only single characters are allowed as comment and delimiter") + return char + + def _ensure_valid_int(val: int) -> int: if isinstance(val, int): c_val = c_int64(val) @@ -169,11 +176,52 @@ def read_file( """ result = EconfFile(c_void_p(None)) file_name = _encode_str(file_name) - delim = _encode_str(delim) - comment = _encode_str(comment) + delim = _ensure_valid_char(delim) + comment = _ensure_valid_char(comment) err = LIBECONF.econf_readFile(byref(result._ptr), file_name, delim, comment) if err: - raise _exceptions(err, f"read_file failed with error: {err_string(err)}") + _exceptions(err, f"read_file failed with error: {err_string(err)}") + return result + + +def read_file_with_callback( + file_name: str | bytes, + delim: str | bytes, + comment: str | bytes, + callback: Callable[[any], bool], + callback_data: any, +) -> EconfFile: + """ + Read a config file and write the key-value pairs into a keyfile object + + A user defined function will be called in order e.g. to check the correct file permissions. + If the function returns False the parsing will be aborted and an Exception will be raised + + :param file_name: absolute path of file to be parsed + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :param callback: User defined function which will be called and returns a boolean + :param callback_data: argument to be give to the callback function + :return: Key-Value storage object + """ + result = EconfFile(c_void_p(None)) + file_name = _encode_str(file_name) + delim = _ensure_valid_char(delim) + comment = _ensure_valid_char(comment) + + def callback_proxy(fake_data: c_void_p) -> c_bool: + return callback(callback_data) + + CBFUNC = CFUNCTYPE(c_bool, c_void_p) + cb_func = CBFUNC(callback_proxy) + + err = LIBECONF.econf_readFileWithCallback( + byref(result._ptr), file_name, delim, comment, cb_func, c_void_p(None) + ) + if err: + _exceptions( + err, f"read_file_with_callback failed with error: {err_string(err)}" + ) return result @@ -192,7 +240,7 @@ def merge_files(usr_file: EconfFile, etc_file: EconfFile) -> EconfFile: etc_file._ptr, ) if err: - raise _exceptions(err, f"merge_files failed with error: {err_string(err)}") + _exceptions(err, f"merge_files failed with error: {err_string(err)}") return merged_file @@ -219,21 +267,84 @@ def read_dirs( :return: merged EconfFile object """ result = EconfFile(c_void_p()) - c_usr_conf_dir = _encode_str(usr_conf_dir) - c_etc_conf_dir = _encode_str(etc_conf_dir) - c_project_name = _encode_str(project_name) - c_config_suffix = _encode_str(config_suffix) + usr_conf_dir = _encode_str(usr_conf_dir) + etc_conf_dir = _encode_str(etc_conf_dir) + project_name = _encode_str(project_name) + config_suffix = _encode_str(config_suffix) + delim = _ensure_valid_char(delim) + comment = _ensure_valid_char(comment) err = LIBECONF.econf_readDirs( byref(result._ptr), - c_usr_conf_dir, - c_etc_conf_dir, - c_project_name, - c_config_suffix, + usr_conf_dir, + etc_conf_dir, + project_name, + config_suffix, delim, comment, ) if err: - raise _exceptions(err, f"read_dirs failed with error: {err_string(err)}") + _exceptions(err, f"read_dirs failed with error: {err_string(err)}") + return result + + +def read_dirs_with_callback( + usr_conf_dir: str | bytes, + etc_conf_dir: str | bytes, + project_name: str | bytes, + config_suffix: str | bytes, + delim: str | bytes, + comment: str | bytes, + callback: Callable[[any], bool], + callback_data: any, +) -> EconfFile: + """ + Read configuration from the first found config file and merge with snippets from conf.d/ directory + + For every file a user defined function will be called in order e.g. to check the correct file permissions. + If the function returns False the parsing will be aborted and an Exception will be raised + + e.g. searches /usr/etc/ and /etc/ for an example.conf file and merges it with the snippets in either + /usr/etc/example.conf.d/ or /etc/example.conf.d + + :param usr_conf_dir: absolute path of the first directory to be searched + :param etc_conf_dir: absolute path of the second directory to be searched + :param project_name: basename of the configuration file + :param config_suffix: suffix of the configuration file + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :param callback: User defined function which will be called for each file and returns a boolean + :param callback_data: argument to be give to the callback function + :return: merged EconfFile object + """ + result = EconfFile(c_void_p()) + usr_conf_dir = _encode_str(usr_conf_dir) + etc_conf_dir = _encode_str(etc_conf_dir) + project_name = _encode_str(project_name) + config_suffix = _encode_str(config_suffix) + delim = _ensure_valid_char(delim) + comment = _ensure_valid_char(comment) + + def callback_proxy(fake_data: c_void_p): + return callback(callback_data) + + CBFUNC = CFUNCTYPE(c_bool, c_void_p) + cb_func = CBFUNC(callback_proxy) + + err = LIBECONF.econf_readDirsWithCallback( + byref(result._ptr), + usr_conf_dir, + etc_conf_dir, + project_name, + config_suffix, + delim, + comment, + cb_func, + c_void_p(None), + ) + if err: + _exceptions( + err, f"read_dirs_with_callback failed with error: {err_string(err)}" + ) return result @@ -260,26 +371,93 @@ def read_dirs_history( :return: list of EconfFile objects """ key_files = c_void_p(None) - c_size = c_size_t() - c_usr_conf_dir = _encode_str(usr_conf_dir) - c_etc_conf_dir = _encode_str(etc_conf_dir) - c_project_name = _encode_str(project_name) - c_config_suffix = _encode_str(config_suffix) + size = c_size_t() + usr_conf_dir = _encode_str(usr_conf_dir) + etc_conf_dir = _encode_str(etc_conf_dir) + project_name = _encode_str(project_name) + config_suffix = _encode_str(config_suffix) + delim = _ensure_valid_char(delim) + comment = _ensure_valid_char(comment) err = LIBECONF.econf_readDirsHistory( byref(key_files), - byref(c_size), - c_usr_conf_dir, - c_etc_conf_dir, - c_project_name, - c_config_suffix, + byref(size), + usr_conf_dir, + etc_conf_dir, + project_name, + config_suffix, + delim, + comment, + ) + if err: + _exceptions(err, f"read_dirs_history failed with error: {err_string(err)}") + + arr = cast(key_files, POINTER(c_void_p * size.value)) + result = [EconfFile(c_void_p(i)) for i in arr.contents] + return result + + +def read_dirs_history_with_callback( + usr_conf_dir: str | bytes, + etc_conf_dir: str | bytes, + project_name: str | bytes, + config_suffix: str | bytes, + delim: str | bytes, + comment: str | bytes, + callback: Callable[[any], bool], + callback_data: any, +) -> EconfFile: + """ + Read configuration from the first found config file and snippets from conf.d/ directory + + For every file a user defined function will be called in order e.g. to check the correct file permissions. + If the function returns False the parsing will be aborted and an Exception will be raised + + e.g. searches /usr/etc/ and /etc/ for an example.conf file and the snippets in either + /usr/etc/example.conf.d/ or /etc/example.conf.d + + :param usr_conf_dir: absolute path of the first directory to be searched + :param etc_conf_dir: absolute path of the second directory to be searched + :param project_name: basename of the configuration file + :param config_suffix: suffix of the configuration file + :param delim: delimiter of a key/value e.g. '=' + :param comment: string that defines the start of a comment e.g. '#' + :param callback: User defined function which will be called for each file and returns a boolean + :param callback_data: argument to be give to the callback function + :return: list of EconfFile objects + """ + key_files = c_void_p(None) + size = c_size_t() + usr_conf_dir = _encode_str(usr_conf_dir) + etc_conf_dir = _encode_str(etc_conf_dir) + project_name = _encode_str(project_name) + config_suffix = _encode_str(config_suffix) + delim = _ensure_valid_char(delim) + comment = _ensure_valid_char(comment) + + def callback_proxy(fake_data: c_void_p): + return callback(callback_data) + + CBFUNC = CFUNCTYPE(c_bool, c_void_p) + cb_func = CBFUNC(callback_proxy) + + err = LIBECONF.econf_readDirsHistoryWithCallback( + byref(key_files), + byref(size), + usr_conf_dir, + etc_conf_dir, + project_name, + config_suffix, delim, comment, + cb_func, + c_void_p(None), ) if err: - raise _exceptions( - err, f"read_dirs_history failed with error: {err_string(err)}" + _exceptions( + err, f"read_dirs_history_with_callback failed with error: {err_string(err)}" ) - arr = cast(key_files, POINTER(c_void_p * c_size.value)) + + arr = cast(key_files, POINTER(c_void_p * size.value)) result = [EconfFile(c_void_p(i)) for i in arr.contents] return result @@ -293,11 +471,11 @@ def new_key_file(delim: str | bytes, comment: str | bytes) -> EconfFile: :return: created EconfFile object """ result = EconfFile(c_void_p()) - delim = _encode_str(delim) - comment = _encode_str(comment) + delim = c_char(_ensure_valid_char(delim)) + comment = c_char(_ensure_valid_char(comment)) err = LIBECONF.econf_newKeyFile(byref(result._ptr), delim, comment) if err: - raise _exceptions(err, f"new_key_file failed with error: {err_string(err)}") + _exceptions(err, f"new_key_file failed with error: {err_string(err)}") return result @@ -310,7 +488,7 @@ def new_ini_file() -> EconfFile: result = EconfFile(c_void_p()) err = LIBECONF.econf_newIniFile(byref(result._ptr)) if err: - raise _exceptions(err, f"new_ini_file failed with error: {err_string(err)}") + _exceptions(err, f"new_ini_file failed with error: {err_string(err)}") return result @@ -346,9 +524,7 @@ def set_comment_tag(ef: EconfFile, comment: str | bytes) -> None: :param comment: The desired comment tag character :return: Nothing """ - comment = _encode_str(comment) - if len(comment) > 1: - raise ValueError("Only single characters are allowed") + comment = _ensure_valid_char(comment) c_comment = c_char(comment) LIBECONF.econf_set_comment_tag(ef._ptr, c_comment) @@ -361,9 +537,7 @@ def set_delimiter_tag(ef: EconfFile, delimiter: str | bytes) -> None: :param delimiter: The desired delimiter character :return: Nothing """ - delimiter = _encode_str(delimiter) - if len(delimiter) > 1: - raise ValueError("Only single characters are allowed") + delimiter = _ensure_valid_char(delimiter) c_delimiter = c_char(delimiter) LIBECONF.econf_set_delimiter_tag(ef._ptr, c_delimiter) diff --git a/bindings/python3/test/test_econf.py b/bindings/python3/test/test_econf.py index a0504aa..d3409f9 100644 --- a/bindings/python3/test/test_econf.py +++ b/bindings/python3/test/test_econf.py @@ -7,7 +7,6 @@ FILE = econf.read_file("test/testdata/examples/example.conf", "=", ";") FILE2 = econf.read_file("test/testdata/examples2/example.conf", "=", "#") -INVALID_FILE = econf.read_file("test/testdata/examples/invalid.conf", ":", "#") @contextmanager @@ -15,6 +14,10 @@ def does_not_raise(): yield +def user_function(value: str) -> bool: + return value == "correct" + + @pytest.mark.parametrize( "value,expected,context", [ @@ -27,6 +30,22 @@ def test_encode_str(value, expected, context): with context: assert econf._encode_str(value) == expected +@pytest.mark.parametrize( + "context,value", + [ + (does_not_raise(), "#"), + (does_not_raise(), b'+'), + (pytest.raises(TypeError), 3), + (pytest.raises(ValueError), "abc") + ] +) +def test_ensure_valid_char(context, value): + with context: + result = econf._ensure_valid_char(value) + + assert len(result) == 1 + assert isinstance(result, bytes) + @pytest.mark.parametrize( "value,context", @@ -61,21 +80,60 @@ def test_ensure_valid_uint(value, context): assert result.value == value -def test_read_file(): - file = "test/testdata/examples/example.conf" - delim = "=" - comment = "#" +@pytest.mark.parametrize( + "file,context", + [ + ("test/testdata/examples/example.conf", does_not_raise()), + ("test/testdata/examples/invalid.conf", pytest.raises(SyntaxError)), + ("test/testdata/examples/fakefile.conf", pytest.raises(FileNotFoundError)) + ] +) +def test_read_file(file, context): + with context: + result = econf.read_file(file, "=", "#") - ef = econf.read_file(file, delim, comment) + assert result._ptr != None + assert econf.get_groups(result) != None + assert econf.get_keys(result, None) != None + assert econf.delimiter_tag(result) == "=" + assert econf.comment_tag(result) == "#" - assert ef._ptr != None +@pytest.mark.parametrize( + "file,context,data", + [ + ("test/testdata/examples/example.conf", does_not_raise(), "correct"), + ("test/testdata/examples/example.conf", pytest.raises(Exception, match="parsing callback has failed"), "wrong"), + ("test/testdata/examples/fakefile.conf", pytest.raises(FileNotFoundError), "correct"), + ("test/testdata/examples/invalid.conf", pytest.raises(SyntaxError), "correct") + ] +) +def test_read_file_with_callback(file, context, data): + with context: + result = econf.read_file_with_callback(file, "=", "#", user_function, data) -def test_new_key_file(): - result = econf.new_key_file("=", "#") + assert result._ptr != None + assert econf.get_groups(result) != None + assert econf.get_keys(result, None) != None + assert econf.delimiter_tag(result) == "=" + assert econf.comment_tag(result) == "#" - assert result - assert type(result) == econf.EconfFile +@pytest.mark.parametrize( + "context,delim,comment", + [ + (does_not_raise(), "=", "#"), + (pytest.raises(ValueError), "abc", "def"), + (pytest.raises(TypeError), 1, 2) + ] +) +def test_new_key_file(context, delim, comment): + with context: + result = econf.new_key_file(delim, comment) + + assert result + assert type(result) == econf.EconfFile + assert econf.delimiter_tag(result) == delim + assert econf.comment_tag(result) == comment def test_new_ini_file(): @@ -83,6 +141,8 @@ def test_new_ini_file(): assert result assert type(result) == econf.EconfFile + assert econf.delimiter_tag(result) == "=" + assert econf.comment_tag(result) == "#" def test_merge_files(): @@ -107,6 +167,24 @@ def test_read_dirs(): assert len(econf.get_keys(result, "Group")) == 4 assert len(econf.get_groups(result)) == 3 +@pytest.mark.parametrize( + "context,data", + [ + (does_not_raise(), "correct"), + (pytest.raises(Exception, match="parsing callback has failed"), "wrong") + ] +) +def test_read_dirs_with_callback(context, data): + with context: + usr_dir = "test/testdata/examples2/" + etc_dir = "test/testdata/examples" + name = "example" + result = econf.read_dirs_with_callback(usr_dir, etc_dir, name, "conf", "=", "#", user_function, data) + + assert len(econf.get_keys(result, None)) == 3 + assert len(econf.get_keys(result, "Group")) == 4 + assert len(econf.get_groups(result)) == 3 + def test_read_dirs_history(): result = econf.read_dirs_history( @@ -123,13 +201,31 @@ def test_read_dirs_history(): assert len(econf.get_keys(result[0], None)) == 2 assert len(econf.get_groups(result[1])) == 1 +@pytest.mark.parametrize( + "context,data", + [ + (does_not_raise(), "correct"), + (pytest.raises(Exception, match="parsing callback has failed"), "wrong") + ] +) +def test_read_dirs_history_with_callback(context, data): + with context: + usr_dir = "test/testdata/examples2/" + etc_dir = "test/testdata/examples" + name = "example" + result = econf.read_dirs_history_with_callback(usr_dir, etc_dir, name, "conf", "=", "#", user_function, data) + + assert len(result) == 2 + assert len(econf.get_groups(result[0])) == 3 + assert len(econf.get_keys(result[0], None)) == 2 + assert len(econf.get_groups(result[1])) == 1 + @pytest.mark.parametrize( "ef,context,expected", [ (FILE, does_not_raise(), ";"), (FILE2, does_not_raise(), "#"), - (INVALID_FILE, does_not_raise(), "#"), ], ) def test_comment_tag(ef, context, expected): @@ -144,7 +240,6 @@ def test_comment_tag(ef, context, expected): [ (FILE, does_not_raise(), "="), (FILE2, does_not_raise(), "="), - (INVALID_FILE, does_not_raise(), ":"), ], ) def test_delimiter_tag(ef, context, expected): diff --git a/bindings/python3/test/testdata/examples/invalid.conf b/bindings/python3/test/testdata/examples/invalid.conf index 247fa78..6be5d48 100644 --- a/bindings/python3/test/testdata/examples/invalid.conf +++ b/bindings/python3/test/testdata/examples/invalid.conf @@ -1,2 +1,6 @@ +[valid] foo -abc \ No newline at end of file +abc + +[empty key += 5 \ No newline at end of file From 8262ef590315fd4749ddd6bd9f7812781eb78b71 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Thu, 19 Oct 2023 17:54:40 +0200 Subject: [PATCH 11/13] update man page and version --- bindings/python3/docs/python-libeconf.3 | 205 +++++++++++++++++++++++- bindings/python3/pyproject.toml | 6 +- 2 files changed, 202 insertions(+), 9 deletions(-) diff --git a/bindings/python3/docs/python-libeconf.3 b/bindings/python3/docs/python-libeconf.3 index 5fb1e3e..4320156 100644 --- a/bindings/python3/docs/python-libeconf.3 +++ b/bindings/python3/docs/python-libeconf.3 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PYTHON-LIBECONF" "3" "Oct 18, 2023" "" "python-libeconf" +.TH "PYTHON-LIBECONF" "3" "Oct 19, 2023" "" "python-libeconf" .sp \fBpython\-libeconf\fP is a Python Library which offers Python bindings for \fI\%libeconf\fP\&. @@ -48,13 +48,6 @@ To use this project please make sure you have libeconf installed on your system! .UNINDENT .UNINDENT .sp -\fBNOTE:\fP -.INDENT 0.0 -.INDENT 3.5 -The package is not yet published on pypi. This is coming soon. -.UNINDENT -.UNINDENT -.sp You can install this project from pypi with .INDENT 0.0 .INDENT 3.5 @@ -109,6 +102,33 @@ Key\-Value storage object .UNINDENT .INDENT 0.0 .TP +.B econf.read_file_with_callback(file_name: str | bytes, delim: str | bytes, comment: str | bytes, callback: Callable[[any], bool], callback_data: any) -> EconfFile +Read a config file and write the key\-value pairs into a keyfile object +.sp +A user defined function will be called in order e.g. to check the correct file permissions. +If the function returns False the parsing will be aborted and an Exception will be raised +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBfile_name\fP – absolute path of file to be parsed +.IP \(bu 2 +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ +.IP \(bu 2 +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ +.IP \(bu 2 +\fBcallback\fP – User defined function which will be called and returns a boolean +.IP \(bu 2 +\fBcallback_data\fP – argument to be give to the callback function +.UNINDENT +.TP +.B Returns +Key\-Value storage object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP .B econf.new_key_file(delim: str | bytes, comment: str | bytes) -> EconfFile Create a new empty keyfile .INDENT 7.0 @@ -184,6 +204,42 @@ merged EconfFile object .UNINDENT .INDENT 0.0 .TP +.B econf.read_dirs_with_callback(usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, project_name: str | bytes, config_suffix: str | bytes, delim: str | bytes, comment: str | bytes, callback: Callable[[any], bool], callback_data: any) -> EconfFile +Read configuration from the first found config file and merge with snippets from conf.d/ directory +.sp +For every file a user defined function will be called in order e.g. to check the correct file permissions. +If the function returns False the parsing will be aborted and an Exception will be raised +.sp +e.g. searches /usr/etc/ and /etc/ for an example.conf file and merges it with the snippets in either +/usr/etc/example.conf.d/ or /etc/example.conf.d +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBusr_conf_dir\fP – absolute path of the first directory to be searched +.IP \(bu 2 +\fBetc_conf_dir\fP – absolute path of the second directory to be searched +.IP \(bu 2 +\fBproject_name\fP – basename of the configuration file +.IP \(bu 2 +\fBconfig_suffix\fP – suffix of the configuration file +.IP \(bu 2 +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ +.IP \(bu 2 +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ +.IP \(bu 2 +\fBcallback\fP – User defined function which will be called for each file and returns a boolean +.IP \(bu 2 +\fBcallback_data\fP – argument to be give to the callback function +.UNINDENT +.TP +.B Returns +merged EconfFile object +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP .B econf.read_dirs_history(usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, project_name: str | bytes, config_suffix: str | bytes, delim: str | bytes, comment: str | bytes) -> list[EconfFile] Read configuration from the first found config file and snippets from conf.d/ directory .sp @@ -213,6 +269,104 @@ list of EconfFile objects .UNINDENT .INDENT 0.0 .TP +.B econf.read_dirs_history_with_callback(usr_conf_dir: str | bytes, etc_conf_dir: str | bytes, project_name: str | bytes, config_suffix: str | bytes, delim: str | bytes, comment: str | bytes, callback: Callable[[any], bool], callback_data: any) -> EconfFile +Read configuration from the first found config file and snippets from conf.d/ directory +.sp +For every file a user defined function will be called in order e.g. to check the correct file permissions. +If the function returns False the parsing will be aborted and an Exception will be raised +.sp +e.g. searches /usr/etc/ and /etc/ for an example.conf file and the snippets in either +/usr/etc/example.conf.d/ or /etc/example.conf.d +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBusr_conf_dir\fP – absolute path of the first directory to be searched +.IP \(bu 2 +\fBetc_conf_dir\fP – absolute path of the second directory to be searched +.IP \(bu 2 +\fBproject_name\fP – basename of the configuration file +.IP \(bu 2 +\fBconfig_suffix\fP – suffix of the configuration file +.IP \(bu 2 +\fBdelim\fP – delimiter of a key/value e.g. ‘=’ +.IP \(bu 2 +\fBcomment\fP – string that defines the start of a comment e.g. ‘#’ +.IP \(bu 2 +\fBcallback\fP – User defined function which will be called for each file and returns a boolean +.IP \(bu 2 +\fBcallback_data\fP – argument to be give to the callback function +.UNINDENT +.TP +.B Returns +list of EconfFile objects +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.comment_tag(ef: EconfFile) -> str +Get the comment tag of the specified EconfFile +.INDENT 7.0 +.TP +.B Parameters +\fBef\fP – Key\-Value storage object +.TP +.B Returns +The comment tag of the EconfFile +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.set_comment_tag(ef: EconfFile, comment: str | bytes) -> None +Set the comment tag of the specified EconfFile +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP – Key\-Value storage object +.IP \(bu 2 +\fBcomment\fP – The desired comment tag character +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.delimiter_tag(ef: EconfFile) -> str +Get the delimiter tag of the specified EconfFile +.INDENT 7.0 +.TP +.B Parameters +\fBef\fP – Key\-Value storage object +.TP +.B Returns +the delimiter tag of the EconfFile +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP +.B econf.set_delimiter_tag(ef: EconfFile, delimiter: str | bytes) -> None +Set the delimiter tag of the specified EconfFile +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP – Key\-Value storage object +.IP \(bu 2 +\fBdelimiter\fP – The desired delimiter character +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP .B econf.write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> None Write content of a keyfile to specified location .INDENT 7.0 @@ -231,6 +385,19 @@ Write content of a keyfile to specified location Nothing .UNINDENT .UNINDENT +.INDENT 0.0 +.TP +.B econf.get_path(ef: EconfFile) -> str +Get the path of the source of the given key file +.INDENT 7.0 +.TP +.B Parameters +\fBef\fP – Key\-Value storage object +.TP +.B Returns +path of the config file as string +.UNINDENT +.UNINDENT .SS Functions for getting values .INDENT 0.0 .TP @@ -477,6 +644,28 @@ value of the key .SS Functions for setting values .INDENT 0.0 .TP +.B econf.set_value(ef: EconfFile, group: str | bytes, key: str | bytes, value: int | float | str | bool) -> None +Dynamically set a value in a keyfile and returns a status code +.INDENT 7.0 +.TP +.B Parameters +.INDENT 7.0 +.IP \(bu 2 +\fBef\fP – EconfFile object to set value in +.IP \(bu 2 +\fBgroup\fP – group of the key to be changed +.IP \(bu 2 +\fBkey\fP – key to be changed +.IP \(bu 2 +\fBvalue\fP – desired value +.UNINDENT +.TP +.B Returns +Nothing +.UNINDENT +.UNINDENT +.INDENT 0.0 +.TP .B econf.set_int_value(ef: EconfFile, group: str, key: str, value: int) -> None Setting an integer value for given group/key .INDENT 7.0 diff --git a/bindings/python3/pyproject.toml b/bindings/python3/pyproject.toml index 18d0a3f..6b83f0b 100644 --- a/bindings/python3/pyproject.toml +++ b/bindings/python3/pyproject.toml @@ -4,9 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "python-libeconf" -version = "1.0.0" +version = "1.1.0" description = "Python bindings for libeconf" authors = [{name="nkrapp", email="nico.krapp@suse.com"}] +readme = "README.md" [project.optional-dependencies] doc_requires = [ @@ -16,3 +17,6 @@ doc_requires = [ test_requires = [ "pytest" ] + +[tool.isort] +profile = "black" From 815c8be2915f241d51f9b89c7666fb85fa2c9e71 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Fri, 20 Oct 2023 13:40:44 +0200 Subject: [PATCH 12/13] Fix error checking and add missing tests --- bindings/python3/docs/python-libeconf.3 | 4 ++- bindings/python3/econf.py | 6 +++- bindings/python3/test/test_econf.py | 48 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/bindings/python3/docs/python-libeconf.3 b/bindings/python3/docs/python-libeconf.3 index 4320156..64c5341 100644 --- a/bindings/python3/docs/python-libeconf.3 +++ b/bindings/python3/docs/python-libeconf.3 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PYTHON-LIBECONF" "3" "Oct 19, 2023" "" "python-libeconf" +.TH "PYTHON-LIBECONF" "3" "Oct 20, 2023" "" "python-libeconf" .sp \fBpython\-libeconf\fP is a Python Library which offers Python bindings for \fI\%libeconf\fP\&. @@ -779,6 +779,8 @@ Nothing .TP .B econf.free_file(ef: EconfFile) Free the memory of a given keyfile +.sp +This function is called automatically at the end of every objects lifetime and should not be used otherwise .INDENT 7.0 .TP .B Parameters diff --git a/bindings/python3/econf.py b/bindings/python3/econf.py index f6530a6..b213521 100644 --- a/bindings/python3/econf.py +++ b/bindings/python3/econf.py @@ -953,16 +953,20 @@ def err_location() -> Tuple[str, int]: c_filename = c_char_p() c_line_nr = c_uint64() LIBECONF.econf_errLocation(byref(c_filename), byref(c_line_nr)) - return c_filename.value, c_line_nr.value + return c_filename.value.decode("utf-8"), c_line_nr.value def free_file(ef: EconfFile): """ Free the memory of a given keyfile + This function is called automatically at the end of every objects lifetime and should not be used otherwise + :param ef: EconfFile to be freed :return: None """ + if not isinstance(ef, EconfFile): + raise TypeError("Parameter must be an EconfFile object") if not ef._ptr: return LIBECONF.econf_freeFile(ef._ptr) diff --git a/bindings/python3/test/test_econf.py b/bindings/python3/test/test_econf.py index d3409f9..fe1bcaf 100644 --- a/bindings/python3/test/test_econf.py +++ b/bindings/python3/test/test_econf.py @@ -287,3 +287,51 @@ def test_write_file(tmp_path): result = econf.write_file(FILE, d, name) assert (tmp_path / "example.conf").exists() + + +@pytest.mark.parametrize( + "context,value,expected", + [ + (does_not_raise(), 0, "Success"), + (does_not_raise(), 5, "Key not found"), + (does_not_raise(), 23, "Unknown libeconf error 23"), + (pytest.raises(TypeError), "", "") + ] +) +def test_err_string(context, value, expected): + with context: + result = econf.err_string(value) + + assert result == expected + + +def test_err_location(): + file, line = econf.err_location() + + assert isinstance(file, str) + assert isinstance(line, int) + + +@pytest.mark.parametrize( + "file,context", + [ + #(FILE, does_not_raise()), + (econf.EconfFile(c_void_p(None)), does_not_raise()), + (5, pytest.raises(TypeError)) + ] +) +def test_free_file(file, context): + with context: + econf.free_file(file) + +@pytest.mark.parametrize( + "context,list", + [ + (does_not_raise(), ["/", "/conf.d/", None]), + (does_not_raise(), []), + (pytest.raises(TypeError), "") + ] +) +def test_set_conf_dirs(context, list): + with context: + econf.set_conf_dirs(list) From 5fb87cc720c147a719896facff9c377e7e2d1ec4 Mon Sep 17 00:00:00 2001 From: nkrapp Date: Fri, 20 Oct 2023 13:55:59 +0200 Subject: [PATCH 13/13] improve error enum and exception raising --- bindings/python3/econf.py | 202 ++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 115 deletions(-) diff --git a/bindings/python3/econf.py b/bindings/python3/econf.py index b213521..fba92b6 100644 --- a/bindings/python3/econf.py +++ b/bindings/python3/econf.py @@ -25,76 +25,54 @@ def __del__(self): free_file(self) -class Econf_err(Enum): - ECONF_SUCCESS = 0 - ECONF_ERROR = 1 - ECONF_NOMEM = 2 - ECONF_NOFILE = 3 - ECONF_NOGROUP = 4 - ECONF_NOKEY = 5 - ECONF_EMPTYKEY = 6 - ECONF_WRITEERROR = 7 - ECONF_PARSE_ERROR = 8 - ECONF_MISSING_BRACKET = 9 - ECONF_MISSING_DELIMITER = 10 - ECONF_EMPTY_SECTION_NAME = 11 - ECONF_TEXT_AFTER_SECTION = 12 - ECONF_FILE_LIST_IS_NULL = 13 - ECONF_WRONG_BOOLEAN_VALUE = 14 - ECONF_KEY_HAS_NULL_VALUE = 15 - ECONF_WRONG_OWNER = 16 - ECONF_WRONG_GROUP = 17 - ECONF_WRONG_FILE_PERMISSION = 18 - ECONF_WRONG_DIR_PERMISSION = 19 - ECONF_ERROR_FILE_IS_SYM_LINK = 20 - ECONF_PARSING_CALLBACK_FAILED = 21 - - -def _exceptions(err: int, val: str): - if Econf_err(err) == Econf_err.ECONF_ERROR: - raise Exception(val) - elif Econf_err(err) == Econf_err.ECONF_NOMEM: - raise MemoryError(val) - elif Econf_err(err) == Econf_err.ECONF_NOFILE: - raise FileNotFoundError(val) - elif Econf_err(err) == Econf_err.ECONF_NOGROUP: - raise KeyError(val) - elif Econf_err(err) == Econf_err.ECONF_NOKEY: - raise KeyError(val) - elif Econf_err(err) == Econf_err.ECONF_EMPTYKEY: - raise KeyError(val) - elif Econf_err(err) == Econf_err.ECONF_WRITEERROR: - raise OSError(val) - elif Econf_err(err) == Econf_err.ECONF_PARSE_ERROR: - raise Exception(val) - elif Econf_err(err) == Econf_err.ECONF_MISSING_BRACKET: - raise SyntaxError(val) - elif Econf_err(err) == Econf_err.ECONF_MISSING_DELIMITER: - raise SyntaxError(val) - elif Econf_err(err) == Econf_err.ECONF_EMPTY_SECTION_NAME: - raise SyntaxError(val) - elif Econf_err(err) == Econf_err.ECONF_TEXT_AFTER_SECTION: - raise SyntaxError(val) - elif Econf_err(err) == Econf_err.ECONF_FILE_LIST_IS_NULL: - raise ValueError(val) - elif Econf_err(err) == Econf_err.ECONF_WRONG_BOOLEAN_VALUE: - raise ValueError(val) - elif Econf_err(err) == Econf_err.ECONF_KEY_HAS_NULL_VALUE: - raise ValueError(val) - elif Econf_err(err) == Econf_err.ECONF_WRONG_OWNER: - raise PermissionError(val) - elif Econf_err(err) == Econf_err.ECONF_WRONG_GROUP: - raise PermissionError(val) - elif Econf_err(err) == Econf_err.ECONF_WRONG_FILE_PERMISSION: - raise PermissionError(val) - elif Econf_err(err) == Econf_err.ECONF_WRONG_DIR_PERMISSION: - raise PermissionError(val) - elif Econf_err(err) == Econf_err.ECONF_ERROR_FILE_IS_SYM_LINK: - raise FileNotFoundError(val) - elif Econf_err(err) == Econf_err.ECONF_PARSING_CALLBACK_FAILED: - raise Exception(val) - else: - raise Exception(val) +class EconfError(Enum): + SUCCESS = 0 + ERROR = 1 + NOMEM = 2 + NOFILE = 3 + NOGROUP = 4 + NOKEY = 5 + EMPTYKEY = 6 + WRITEERROR = 7 + PARSE_ERROR = 8 + MISSING_BRACKET = 9 + MISSING_DELIMITER = 10 + EMPTY_SECTION_NAME = 11 + TEXT_AFTER_SECTION = 12 + FILE_LIST_IS_NULL = 13 + WRONG_BOOLEAN_VALUE = 14 + KEY_HAS_NULL_VALUE = 15 + WRONG_OWNER = 16 + WRONG_GROUP = 17 + WRONG_FILE_PERMISSION = 18 + WRONG_DIR_PERMISSION = 19 + ERROR_FILE_IS_SYM_LINK = 20 + PARSING_CALLBACK_FAILED = 21 + + +ECONF_EXCEPTION = { + EconfError.ERROR: Exception, + EconfError.NOMEM: MemoryError, + EconfError.NOFILE: FileNotFoundError, + EconfError.NOGROUP: KeyError, + EconfError.NOKEY: KeyError, + EconfError.EMPTYKEY: KeyError, + EconfError.WRITEERROR: OSError, + EconfError.PARSE_ERROR: Exception, + EconfError.MISSING_BRACKET: SyntaxError, + EconfError.MISSING_DELIMITER: SyntaxError, + EconfError.EMPTY_SECTION_NAME: SyntaxError, + EconfError.TEXT_AFTER_SECTION: SyntaxError, + EconfError.FILE_LIST_IS_NULL: ValueError, + EconfError.WRONG_BOOLEAN_VALUE: ValueError, + EconfError.KEY_HAS_NULL_VALUE: ValueError, + EconfError.WRONG_OWNER: PermissionError, + EconfError.WRONG_GROUP: PermissionError, + EconfError.WRONG_FILE_PERMISSION: PermissionError, + EconfError.WRONG_DIR_PERMISSION: PermissionError, + EconfError.ERROR_FILE_IS_SYM_LINK: PermissionError, + EconfError.PARSING_CALLBACK_FAILED: Exception +} def _encode_str(string: str | bytes) -> bytes: @@ -180,7 +158,7 @@ def read_file( comment = _ensure_valid_char(comment) err = LIBECONF.econf_readFile(byref(result._ptr), file_name, delim, comment) if err: - _exceptions(err, f"read_file failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"read_file failed with error: {err_string(err)}") return result @@ -219,8 +197,8 @@ def callback_proxy(fake_data: c_void_p) -> c_bool: byref(result._ptr), file_name, delim, comment, cb_func, c_void_p(None) ) if err: - _exceptions( - err, f"read_file_with_callback failed with error: {err_string(err)}" + raise ECONF_EXCEPTION[EconfError(err)]( + f"read_file_with_callback failed with error: {err_string(err)}" ) return result @@ -240,7 +218,7 @@ def merge_files(usr_file: EconfFile, etc_file: EconfFile) -> EconfFile: etc_file._ptr, ) if err: - _exceptions(err, f"merge_files failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"merge_files failed with error: {err_string(err)}") return merged_file @@ -283,7 +261,7 @@ def read_dirs( comment, ) if err: - _exceptions(err, f"read_dirs failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"read_dirs failed with error: {err_string(err)}") return result @@ -342,8 +320,8 @@ def callback_proxy(fake_data: c_void_p): c_void_p(None), ) if err: - _exceptions( - err, f"read_dirs_with_callback failed with error: {err_string(err)}" + raise ECONF_EXCEPTION[EconfError(err)]( + f"read_dirs_with_callback failed with error: {err_string(err)}" ) return result @@ -389,7 +367,7 @@ def read_dirs_history( comment, ) if err: - _exceptions(err, f"read_dirs_history failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"read_dirs_history failed with error: {err_string(err)}") arr = cast(key_files, POINTER(c_void_p * size.value)) result = [EconfFile(c_void_p(i)) for i in arr.contents] @@ -453,8 +431,8 @@ def callback_proxy(fake_data: c_void_p): c_void_p(None), ) if err: - _exceptions( - err, f"read_dirs_history_with_callback failed with error: {err_string(err)}" + raise ECONF_EXCEPTION[EconfError(err)]( + f"read_dirs_history_with_callback failed with error: {err_string(err)}" ) arr = cast(key_files, POINTER(c_void_p * size.value)) @@ -475,7 +453,7 @@ def new_key_file(delim: str | bytes, comment: str | bytes) -> EconfFile: comment = c_char(_ensure_valid_char(comment)) err = LIBECONF.econf_newKeyFile(byref(result._ptr), delim, comment) if err: - _exceptions(err, f"new_key_file failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"new_key_file failed with error: {err_string(err)}") return result @@ -488,7 +466,7 @@ def new_ini_file() -> EconfFile: result = EconfFile(c_void_p()) err = LIBECONF.econf_newIniFile(byref(result._ptr)) if err: - _exceptions(err, f"new_ini_file failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"new_ini_file failed with error: {err_string(err)}") return result @@ -555,7 +533,7 @@ def write_file(ef: EconfFile, save_to_dir: str, file_name: str) -> None: c_file_name = _encode_str(file_name) err = LIBECONF.econf_writeFile(byref(ef._ptr), c_save_to_dir, c_file_name) if err: - _exceptions(err, f"write_file failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"write_file failed with error: {err_string(err)}") def get_path(ef: EconfFile) -> str: @@ -581,7 +559,7 @@ def get_groups(ef: EconfFile) -> list[str]: c_groups = c_void_p(None) err = LIBECONF.econf_getGroups(ef._ptr, byref(c_length), byref(c_groups)) if err: - _exceptions(err, f"get_groups failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_groups failed with error: {err_string(err)}") arr = cast(c_groups, POINTER(c_char_p * c_length.value)) result = [i.decode("utf-8") for i in arr.contents] return result @@ -601,7 +579,7 @@ def get_keys(ef: EconfFile, group: str) -> list[str]: group = _encode_str(group) err = LIBECONF.econf_getKeys(ef._ptr, group, byref(c_length), byref(c_keys)) if err: - _exceptions(err, f"get_keys failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_keys failed with error: {err_string(err)}") arr = cast(c_keys, POINTER(c_char_p * c_length.value)) result = [i.decode("utf-8") for i in arr.contents] return result @@ -622,7 +600,7 @@ def get_int_value(ef: EconfFile, group: str, key: str) -> int: c_result = c_int64() err = LIBECONF.econf_getInt64Value(ef._ptr, group, c_key, byref(c_result)) if err: - _exceptions(err, f"get_int64_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_int64_value failed with error: {err_string(err)}") return c_result.value @@ -641,7 +619,7 @@ def get_uint_value(ef: EconfFile, group: str, key: str) -> int: c_result = c_uint64() err = LIBECONF.econf_getUInt64Value(ef._ptr, group, c_key, byref(c_result)) if err: - _exceptions(err, f"get_uint64_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_uint64_value failed with error: {err_string(err)}") return c_result.value @@ -660,7 +638,7 @@ def get_float_value(ef: EconfFile, group: str, key: str) -> float: c_result = c_double() err = LIBECONF.econf_getDoubleValue(ef._ptr, group, c_key, byref(c_result)) if err: - _exceptions(err, f"get_double_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_double_value failed with error: {err_string(err)}") return c_result.value @@ -679,7 +657,7 @@ def get_string_value(ef: EconfFile, group: str, key: str) -> str: c_result = c_char_p() err = LIBECONF.econf_getStringValue(ef._ptr, group, c_key, byref(c_result)) if err: - _exceptions(err, f"get_string_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_string_value failed with error: {err_string(err)}") return c_result.value.decode("utf-8") @@ -698,7 +676,7 @@ def get_bool_value(ef: EconfFile, group: str, key: str) -> bool: c_result = c_bool() err = LIBECONF.econf_getBoolValue(ef._ptr, group, c_key, byref(c_result)) if err: - _exceptions(err, f"get_bool_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_bool_value failed with error: {err_string(err)}") return c_result.value @@ -720,10 +698,8 @@ def get_int_value_def(ef: EconfFile, group: str, key: str, default: int) -> int: err = LIBECONF.econf_getInt64ValueDef( ef._ptr, group, c_key, byref(c_result), c_default ) - if err: - if Econf_err(err) == Econf_err.ECONF_NOKEY: - return c_default.value - _exceptions(err, f"get_int64_value_def failed with error: {err_string(err)}") + if err and EconfError(err) != EconfError.NOKEY: + raise ECONF_EXCEPTION[EconfError(err)](f"get_int64_value_def failed with error: {err_string(err)}") return c_result.value @@ -745,10 +721,8 @@ def get_uint_value_def(ef: EconfFile, group: str, key: str, default: int) -> int err = LIBECONF.econf_getUInt64ValueDef( ef._ptr, group, c_key, byref(c_result), c_default ) - if err: - if Econf_err(err) == Econf_err.ECONF_NOKEY: - return c_default.value - _exceptions(err, f"get_uint64_value_def failed with error: {err_string(err)}") + if err and EconfError(err) != EconfError.NOKEY: + raise ECONF_EXCEPTION[EconfError(err)](f"get_uint64_value_def failed with error: {err_string(err)}") return c_result.value @@ -772,10 +746,8 @@ def get_float_value_def(ef: EconfFile, group: str, key: str, default: float) -> err = LIBECONF.econf_getDoubleValueDef( ef._ptr, group, c_key, byref(c_result), c_default ) - if err: - if Econf_err(err) == Econf_err.ECONF_NOKEY: - return c_default.value - _exceptions(err, f"get_double_value_def failed with error: {err_string(err)}") + if err and EconfError(err) != EconfError.NOKEY: + raise ECONF_EXCEPTION[EconfError(err)](f"get_double_value_def failed with error: {err_string(err)}") return c_result.value @@ -798,9 +770,9 @@ def get_string_value_def(ef: EconfFile, group: str, key: str, default: str) -> s ef._ptr, group, c_key, byref(c_result), c_default ) if err: - if Econf_err(err) == Econf_err.ECONF_NOKEY: + if EconfError(err) == EconfError.NOKEY: return c_default.decode("utf-8") - _exceptions(err, f"get_string_value_def failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"get_string_value_def failed with error: {err_string(err)}") return c_result.value.decode("utf-8") @@ -824,10 +796,8 @@ def get_bool_value_def(ef: EconfFile, group: str, key: str, default: bool) -> bo err = LIBECONF.econf_getBoolValueDef( ef._ptr, group, c_key, byref(c_result), c_default ) - if err: - if Econf_err(err) == Econf_err.ECONF_NOKEY: - return c_default.value - _exceptions(err, f"get_bool_value_def failed with error: {err_string(err)}") + if err and EconfError(err) != EconfError.NOKEY: + raise ECONF_EXCEPTION[EconfError(err)](f"get_bool_value_def failed with error: {err_string(err)}") return c_result.value @@ -847,7 +817,7 @@ def set_int_value(ef: EconfFile, group: str, key: str, value: int) -> None: c_value = _ensure_valid_int(value) err = LIBECONF.econf_setInt64Value(ef._ptr, group, c_key, c_value) if err: - _exceptions(err, f"set_int64_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"set_int64_value failed with error: {err_string(err)}") def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None: @@ -866,7 +836,7 @@ def set_uint_value(ef: EconfFile, group: str, key: str, value: int) -> None: c_value = _ensure_valid_uint(value) err = LIBECONF.econf_setUInt64Value(ef._ptr, group, c_key, c_value) if err: - _exceptions(err, f"set_uint64_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"set_uint64_value failed with error: {err_string(err)}") def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None: @@ -887,7 +857,7 @@ def set_float_value(ef: EconfFile, group: str, key: str, value: float) -> None: c_value = c_double(value) err = LIBECONF.econf_setDoubleValue(ef._ptr, group, c_key, c_value) if err: - _exceptions(err, f"set_double_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"set_double_value failed with error: {err_string(err)}") def set_string_value(ef: EconfFile, group: str, key: str, value: str | bytes) -> None: @@ -906,7 +876,7 @@ def set_string_value(ef: EconfFile, group: str, key: str, value: str | bytes) -> c_value = _encode_str(value) err = LIBECONF.econf_setStringValue(ef._ptr, group, c_key, c_value) if err: - _exceptions(err, f"set_string_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"set_string_value failed with error: {err_string(err)}") def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: @@ -927,7 +897,7 @@ def set_bool_value(ef: EconfFile, group: str, key: str, value: bool) -> None: c_value = _encode_str(str(value)) err = LIBECONF.econf_setBoolValue(ef._ptr, group, c_key, c_value) if err: - _exceptions(err, f"set_bool_value failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"set_bool_value failed with error: {err_string(err)}") def err_string(error: int) -> str: @@ -990,7 +960,9 @@ def set_conf_dirs(dir_postfix_list: list[str]) -> None: str_arr = c_char_p * len(dir_postfix_list) dir_arr = str_arr() for i in range(len(dir_postfix_list)): - dir_arr[i] = c_char_p(_encode_str(dir_postfix_list[i])) + if dir_postfix_list[i] is not None: + dir_postfix_list[i] = _encode_str(dir_postfix_list[i]) + dir_arr[i] = c_char_p(dir_postfix_list[i]) err = LIBECONF.econf_set_conf_dirs(dir_arr) if err: - _exceptions(err, f"set_conf_dirs failed with error: {err_string(err)}") + raise ECONF_EXCEPTION[EconfError(err)](f"set_conf_dirs failed with error: {err_string(err)}")