#!/usr/bin/env python
# cardinal_pythonlib/configfiles.py
"""
===============================================================================
Original code copyright (C) 2009-2022 Rudolf Cardinal (rudolf@pobox.com).
This file is part of cardinal_pythonlib.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===============================================================================
**Support functions for config (.INI) file reading.**
"""
from configparser import ConfigParser, NoOptionError
import logging
from typing import Any, Callable, Iterable, List
from cardinal_pythonlib.logs import get_brace_style_log_with_null_handler
log = get_brace_style_log_with_null_handler(__name__)
# =============================================================================
# Style 1
# =============================================================================
[docs]def get_config_string_option(
parser: ConfigParser, section: str, option: str, default: str = None
) -> str:
"""
Retrieves a string value from a parser.
Args:
parser: instance of :class:`ConfigParser`
section: section name within config file
option: option (variable) name within that section
default: value to return if option is absent
Returns:
string value
Raises:
ValueError: if the section is absent
"""
if not parser.has_section(section):
raise ValueError("config missing section: " + section)
return parser.get(section, option, fallback=default)
[docs]def read_config_string_options(
obj: Any,
parser: ConfigParser,
section: str,
options: Iterable[str],
default: str = None,
) -> None:
"""
Reads config options and writes them as attributes of ``obj``, with
attribute names as per ``options``.
Args:
obj: the object to modify
parser: instance of :class:`ConfigParser`
section: section name within config file
options: option (variable) names within that section
default: value to use for any missing options
Returns:
"""
# enforce_str removed; ConfigParser always returns strings unless asked
# specifically
for o in options:
setattr(
obj,
o,
get_config_string_option(parser, section, o, default=default),
)
[docs]def get_config_multiline_option(
parser: ConfigParser, section: str, option: str, default: List[str] = None
) -> List[str]:
"""
Retrieves a multi-line string value from a parser as a list of strings
(one per line, ignoring blank lines).
Args:
parser: instance of :class:`ConfigParser`
section: section name within config file
option: option (variable) name within that section
default: value to return if option is absent (``None`` is mapped to
``[]``)
Returns:
list of strings
Raises:
ValueError: if the section is absent
"""
default = default or []
if not parser.has_section(section):
raise ValueError("config missing section: " + section)
try:
multiline = parser.get(section, option)
values = [x.strip() for x in multiline.splitlines() if x.strip()]
return values
except NoOptionError:
return default
[docs]def read_config_multiline_options(
obj: Any, parser: ConfigParser, section: str, options: Iterable[str]
) -> None:
"""
This is to :func:`read_config_string_options` as
:func:`get_config_multiline_option` is to :func:`get_config_string_option`.
"""
for o in options:
setattr(obj, o, get_config_multiline_option(parser, section, o))
[docs]def get_config_bool_option(
parser: ConfigParser, section: str, option: str, default: bool = None
) -> bool:
"""
Retrieves a boolean value from a parser.
Args:
parser: instance of :class:`ConfigParser`
section: section name within config file
option: option (variable) name within that section
default: value to return if option is absent
Returns:
Boolean value
Raises:
ValueError: if the section is absent
"""
if not parser.has_section(section):
raise ValueError("config missing section: " + section)
return parser.getboolean(section, option, fallback=default)
# =============================================================================
# Style 2
# =============================================================================
# =============================================================================
# Reading config files: style 2
# =============================================================================
[docs]def get_config_parameter(
config: ConfigParser,
section: str,
param: str,
fn: Callable[[Any], Any],
default: Any = None,
loglevel: int = logging.DEBUG,
) -> Any:
"""
Fetch parameter from ``configparser`` ``.INI`` file.
Args:
config: :class:`ConfigParser` object
section: section name within config file
param: name of parameter within section
fn: function to apply to string parameter (e.g. ``int``)
default: default value
loglevel: log level if default is needed
Returns:
parameter value, or ``None`` if ``default is None``, or ``fn(default)``
"""
try:
value = fn(config.get(section, param))
except (TypeError, ValueError, NoOptionError):
log.log(
level=loglevel,
msg=f"Configuration variable {param} not found or improper in "
f"section [{section}]; using default of {default!r}",
)
if default is None:
value = default
else:
value = fn(default)
return value
[docs]def get_config_parameter_boolean(
config: ConfigParser,
section: str,
param: str,
default: bool,
loglevel: int = logging.DEBUG,
) -> bool:
"""
Get Boolean parameter from ``configparser`` ``.INI`` file.
Args:
config: :class:`ConfigParser` object
section: section name within config file
param: name of parameter within section
default: default value
loglevel: log level if default is needed
Returns:
parameter value, or default
"""
try:
value = config.getboolean(section, param)
except (TypeError, ValueError, NoOptionError):
log.log(
level=loglevel,
msg=f"Configuration variable {param} not found or improper in "
f"section [{section}]; using default of {default!r}",
)
value = default
return value
[docs]def get_config_parameter_loglevel(
config: ConfigParser,
section: str,
param: str,
default: int,
loglevel: int = logging.DEBUG,
) -> int:
"""
Get ``loglevel`` parameter from ``configparser`` ``.INI`` file, e.g.
mapping ``'debug'`` to ``logging.DEBUG``.
Args:
config: :class:`ConfigParser` object
section: section name within config file
param: name of parameter within section
default: default value
loglevel: log level if default is needed
Returns:
parameter value, or default
"""
try:
value = config.get(section, param).lower()
if value == "debug":
return logging.DEBUG # 10
elif value == "info":
return logging.INFO
elif value in ["warn", "warning"]:
return logging.WARN
elif value == "error":
return logging.ERROR
elif value in ["critical", "fatal"]:
return logging.CRITICAL # 50
else:
raise ValueError
except (TypeError, ValueError, NoOptionError, AttributeError):
log.log(
level=loglevel,
msg=f"Configuration variable {param} not found or improper in "
f"section [{section}]; using default of {default!r}",
)
return default
[docs]def get_config_parameter_multiline(
config: ConfigParser,
section: str,
param: str,
default: List[str],
loglevel: int = logging.DEBUG,
) -> List[str]:
"""
Get multi-line string parameter from ``configparser`` ``.INI`` file,
as a list of strings (one per line, ignoring blank lines).
Args:
config: :class:`ConfigParser` object
section: section name within config file
param: name of parameter within section
default: default value
loglevel: log level if default is needed
Returns:
parameter value, or default
"""
try:
multiline = config.get(section, param)
lines = [x.strip() for x in multiline.splitlines()]
return [line for line in lines if line]
except (TypeError, ValueError, NoOptionError):
log.log(
level=loglevel,
msg=f"Configuration variable {param} not found or improper in "
f"section [{section}]; using default of {default!r}",
)
return default