From 5d71c84b6d5c457b0749ada5ee6460763ffe3c2e Mon Sep 17 00:00:00 2001 From: Luna Chen Date: Thu, 31 May 2018 19:10:13 +0100 Subject: [PATCH] Removed config.py, moved general config related code to bnmutils::ConfigParser --- CHANGELOG.rst | 14 +- logme/__version__.py | 2 +- logme/cli/_cli.py | 10 +- logme/cli/_cli_utils.py | 11 +- logme/cli/_upgrade_utils.py | 28 +- logme/color_provider.py | 2 +- logme/config.py | 98 ------ logme/exceptions.py | 4 +- logme/providers.py | 3 +- logme/utils.py | 189 ++++------- requirements.txt | 1 + setup.py | 1 + tests/conftest.py | 2 +- tests/test_cli_package/test_cli.py | 26 +- tests/test_cli_package/test_cli_utils.py | 7 +- tests/test_cli_package/test_upgrade_utils.py | 18 +- tests/test_color_provider.py | 18 +- tests/test_config.py | 151 --------- tests/test_providers_logger_object.py | 2 +- tests/test_providers_logprovider.py | 2 +- tests/test_utils.py | 327 ++++++++----------- 21 files changed, 293 insertions(+), 623 deletions(-) delete mode 100644 logme/config.py delete mode 100644 tests/test_config.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec299e1..21e59d7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,13 +2,19 @@ CHANGELOG ========= -dev -================== +1.2.1 (2018-06-1) +================= + +**Bug Fixes** + +- Error handling for color_provider.py was outputting invalid error message when invalid style was passed in. + **Misc** -- changed utils.py::dict_to_conf() to use ConfigParser.read_dict() -- Changed _cli_utils.py::validate_conf() to use ConfigParser.has_section() +- Removed config.py, and moved everything configuration file related to `bnmutils `_ repository. + Moved everything logme configuration related to utils.py +- Changed exception.py::InvalidConfig to InvalidLoggerConfig diff --git a/logme/__version__.py b/logme/__version__.py index 58d478a..3f262a6 100644 --- a/logme/__version__.py +++ b/logme/__version__.py @@ -1 +1 @@ -__version__ = '1.2.0' +__version__ = '1.2.1' diff --git a/logme/cli/_cli.py b/logme/cli/_cli.py index 1171a1b..9854938 100644 --- a/logme/cli/_cli.py +++ b/logme/cli/_cli.py @@ -2,9 +2,9 @@ from pathlib import Path -from ..config import read_config +from bnmutils import ConfigParser + from ..exceptions import LogmeError -from ..utils import dict_to_config from ..__version__ import __version__ from ._cli_utils import ensure_conf_exist, validate_conf, get_tpl, get_color_tpl @@ -94,7 +94,7 @@ def init(ctx, project_root, override, mkdir, level, formatter, log_path): formatter=formatter, filename=log_path) conf_content.update(master_logging_config) - config = dict_to_config(conf_content) + config = ConfigParser.from_dict(conf_content) abs_path = Path(project_root).resolve() conf_location = abs_path.joinpath('logme.ini') @@ -127,7 +127,7 @@ def add(ctx, project_root, name, level, formatter, log_path): validate_conf(name, logme_conf) conf_content = get_tpl(name, level=level, formatter=formatter, filename=log_path) - config = dict_to_config(conf_content) + config = ConfigParser.from_dict(conf_content) with logme_conf.open('a') as conf: config.write(conf) @@ -153,7 +153,7 @@ def remove(ctx, name, project_root): with ensure_conf_exist(project_root) as logme_conf: - config = read_config(logme_conf) + config = ConfigParser.from_files(logme_conf) config.remove_section(name) with logme_conf.open('w+') as conf: diff --git a/logme/cli/_cli_utils.py b/logme/cli/_cli_utils.py index c9abcbd..ddf0b3d 100644 --- a/logme/cli/_cli_utils.py +++ b/logme/cli/_cli_utils.py @@ -4,9 +4,9 @@ from typing import Union -from ..config import read_config +from bnmutils import ConfigParser from ..exceptions import LogmeError -from ..utils import flatten_config_dict, ensure_dir +from ..utils import ensure_dir @contextmanager @@ -37,7 +37,7 @@ def validate_conf(name: str, ini_file_path: Union[str, Path]): :param name: name of the section to be added :param ini_file_path: path of the logme.ini file """ - config = read_config(ini_file_path) + config = ConfigParser.from_files(ini_file_path) if config.has_section(name): raise LogmeError(f"'{name}' logging config already exists in config file: {ini_file_path}") @@ -57,7 +57,7 @@ def get_color_tpl() -> dict: 'DEBUG': 'GREEN', } - return {'colors': flatten_config_dict(color_config)} + return {'colors': color_config} def get_tpl(name: str, **kwargs: str) -> dict: @@ -97,8 +97,7 @@ def get_tpl(name: str, **kwargs: str) -> dict: map_template(logger_template, kwargs) - # flatten config dictionary - config[name] = flatten_config_dict(logger_template) + config[name] = logger_template return dict(config) diff --git a/logme/cli/_upgrade_utils.py b/logme/cli/_upgrade_utils.py index e76ef0f..45cf8c7 100644 --- a/logme/cli/_upgrade_utils.py +++ b/logme/cli/_upgrade_utils.py @@ -3,11 +3,9 @@ from pathlib import Path from copy import deepcopy -from configparser import NoSectionError +from bnmutils import ConfigParser from ._cli_utils import get_color_tpl -from ..config import read_config -from ..utils import conf_to_dict, dict_to_config, flatten_config_dict NONLOGGER_CONFIGS = ['colors'] @@ -20,20 +18,20 @@ def upgrade_to_latest(config_path: Union[str, Path]): :param config_path: logme.ini file """ - config = read_config(config_path) + # config = read_config(config_path) + config_dict = ConfigParser.from_files(config_path).to_dict() config_dict_updated = {} - _upgrade_with_color_config(config, config_dict_updated) + _upgrade_with_color_config(config_dict, config_dict_updated) - for i in config.sections(): - if i not in NONLOGGER_CONFIGS: - config_dict = conf_to_dict(config.items(i)) - updated = _upgrade_logging_config_section(config_dict) + for k, v in config_dict.items(): + if k not in NONLOGGER_CONFIGS: + updated = _upgrade_logging_config_section(v) - config_dict_updated[i] = flatten_config_dict(updated) + config_dict_updated[k] = updated - new_conf = dict_to_config(config_dict_updated) + new_conf = ConfigParser.from_dict(config_dict_updated) with open(config_path, 'w') as file: new_conf.write(file) @@ -65,19 +63,19 @@ def _upgrade_logging_config_section(config_dict: dict) -> dict: return latest -def _upgrade_with_color_config(config, config_dict_updated: dict): +def _upgrade_with_color_config(config_dict, config_dict_updated: dict): """ -- v1.2.0 update -- Upgrade the new config dict with color config. * This function updates the original 'config_dict_updated'* - :param config: the original config to be passed + :param config_dict: the original config dictionary to be passed :param config_dict_updated: new config dict to be written to 'logme.ini' file """ try: - color_config = {'colors': dict(config.items('colors'))} - except NoSectionError: + color_config = {'colors': config_dict['colors']} + except KeyError: color_config = get_color_tpl() config_dict_updated.update(color_config) diff --git a/logme/color_provider.py b/logme/color_provider.py index ce12d12..bd20622 100644 --- a/logme/color_provider.py +++ b/logme/color_provider.py @@ -63,7 +63,7 @@ def __init__(self, color: str=None, style: str=None, bg: str=None): if e.args[0] == 'reset': self.text_style = {} else: - if color: + if not self.color_map.get(color): message = f"{e} is not a valid color" else: message = f"{e} is not a valid style or background color" diff --git a/logme/config.py b/logme/config.py deleted file mode 100644 index a48a97b..0000000 --- a/logme/config.py +++ /dev/null @@ -1,98 +0,0 @@ -from pathlib import Path -from configparser import ConfigParser, NoSectionError - -from typing import Union - -from .utils import conf_to_dict -from .exceptions import InvalidConfig - - -def get_logger_config(caller_file_path: Union[str, Path], name: str=None) -> dict: - """ - Get logger config as dictionary - - :param caller_file_path: file path of the caller, __file__ - :param name: the name(section in logme.ini) of the config to be passed. (optional, default: 'logme') - - :return: logger configuration dictionary - :raises: InvalidConfig, if name is not in config, or name == 'colors' - """ - if name == 'colors': - raise InvalidConfig(f"'colors' cannot be used as a logger configuration") - - if not name: - name = 'logme' - - return get_config_content(caller_file_path, name=name) - - -def get_color_config(caller_file_path: Union[str, Path]): - """ - Return color configuration dict if 'colors' section exists, return None if not found - """ - try: - return get_config_content(caller_file_path, 'colors') - except InvalidConfig: - return - - -def get_config_content(caller_file_path: Union[str, Path], name: str) -> dict: - """ - Get the config section as a dictionary - - :param caller_file_path: file path of the caller, __file__ - :param name: the section name in an .ini file - - :return: configuration as dict - """ - - init_file_path = get_ini_file_path(caller_file_path) - - config = read_config(init_file_path) - - try: - conf_section = config.items(name) - - return conf_to_dict(conf_section) - except NoSectionError: - raise InvalidConfig(f"'{name}' is not a valid configuration in {init_file_path}") - - -def read_config(file_path: Union[str, Path]) -> ConfigParser: - """ - Read the config file given a file path, - :param file_path: the file path which the ini file is located - - :return: ConfigParser object with section populated - :raises: InvalidConfig, If file *does not exist* or if file is *empty* - """ - config = ConfigParser() - config.optionxform = str - - # Update on config.read on string format file_path to work with older version of python, e.g. 3.6.0 - config.read(str(file_path)) - - if not config.sections(): - raise InvalidConfig(f"{file_path} is not a valid config file.") - - return config - - -def get_ini_file_path(caller_file_path: Union[str, Path]) -> Path: - """ - Get the logme.ini config file path - - :param caller_file_path: file path of the caller, callable.__file__ - - :return: Path object of the logme.ini - """ - conf_path = Path(caller_file_path).parent / 'logme.ini' - - if caller_file_path in [Path(Path(caller_file_path).root).resolve(), - Path(caller_file_path).home().resolve()]: - raise ValueError(f"logme.ini does not exist, please use 'logme init' command in your project root.") - - if not conf_path.exists(): - return get_ini_file_path(Path(caller_file_path).parent) - else: - return conf_path.resolve() diff --git a/logme/exceptions.py b/logme/exceptions.py index 79232ff..2a694d4 100644 --- a/logme/exceptions.py +++ b/logme/exceptions.py @@ -7,10 +7,10 @@ class MisMatchScope(Exception): class InvalidOption(Exception): - """Used when the option in config file is invalid""" + """Used when the an option is invalid""" -class InvalidConfig(Exception): +class InvalidLoggerConfig(Exception): """Used when invalid configuration is passed""" diff --git a/logme/providers.py b/logme/providers.py index 6531745..26922b3 100644 --- a/logme/providers.py +++ b/logme/providers.py @@ -8,8 +8,7 @@ from logging import handlers as logging_handlers from .color_provider import ColorFormatter -from .config import get_logger_config, get_color_config -from .utils import ensure_dir +from .utils import ensure_dir, get_logger_config, get_color_config from .exceptions import InvalidOption, DuplicatedHandler, LogmeError diff --git a/logme/utils.py b/logme/utils.py index 15e68dd..90ff670 100644 --- a/logme/utils.py +++ b/logme/utils.py @@ -1,166 +1,119 @@ -import os -import ast -from typing import List, Union +from typing import Union from pathlib import Path -from contextlib import contextmanager -from configparser import ConfigParser -from .exceptions import InvalidOption, LogmeError +from bnmutils import ConfigParser +from configparser import NoSectionError +from .exceptions import InvalidOption, LogmeError, InvalidLoggerConfig -def conf_to_dict(conf_section: List[tuple]) -> dict: - """ - Converting the Configparser *section* to a dictionary format - :param conf_section: values from config.items('section') or dict(config['section']) +def ensure_dir(dir_path: Union[Path, str], path_type: str='parent'): """ + Ensure the existence of the directory, - return {i[0]: conf_item_to_dict(i[1]) if '\n' in i[1] else str_eval(i[1]) - for i in conf_section} + :param dir_path: the path to be ensured + :param path_type: current - ensure the passed in directory exists, typically used when -def conf_item_to_dict(parse_option: str) -> dict: """ - Map the configuration item(such as 'FileHandler' section) to dict. - * Do not pass in the whole config section!* + path_mapping = { + 'current': Path(dir_path).resolve(), + 'parent': Path(dir_path).parent.resolve() + } - :param: parse_option: values from config.get('section', 'option') - """ try: - str_split = parse_option.strip().split('\n') - mapped_list = list(map(lambda x: x.split(': ', 1), str_split)) - - strip_blank_recursive(mapped_list) - - return dict(mapped_list) - except AttributeError: - raise InvalidOption(f"option passed must be a string value, not type of '{type(parse_option).__name__}'.") - except ValueError: - raise InvalidOption(f"{parse_option} is not a valid option, please follow the convention of 'key: value'") - - -def flatten_config_dict(parse_dict: dict) -> dict: - """ - flatten nested dict for logme configuration file - """ - flattened = {} - - for k, v in parse_dict.items(): - - if isinstance(v, (str, bool)) or v is None: - str_val = str(v) - elif isinstance(v, dict): - val_list = [f'{k1}: {v1}' for k1, v1 in v.items()] - - str_val = '\n' + '\n'.join(val_list) - else: - raise ValueError(f"all values in the dict must be dict or string value, " - f"'{type(v).__name__}' is not allowed!") - - flattened[k] = str_val + dir_abs = path_mapping[path_type] - return flattened + if not dir_abs.exists(): + dir_abs.mkdir(parents=True, exist_ok=True) + except KeyError: + raise InvalidOption(f"{path_type} is not a valid option, please pass in either 'parent' or 'current'") -def dict_to_config(conf_content: dict) -> ConfigParser: +def check_scope(scope: str, options: list) -> bool: """ - Convert a dict to ConfigParser Object - - * options must be flattened as string* - - :param conf_content: nested dict - e.g {'section_name': { - 'option1': 'val1', - 'option2': 'val2', - } - } + check if the scope passed is within the options. + Used both in logme/__init__.py and providers.LogDecorator - :return: configpaser.Configparser """ - config = ConfigParser() - # preserve casing - config.optionxform = str - config.read_dict(conf_content) + if scope not in options: + raise LogmeError(f"scope '{scope}' is not supported, " + f"please use one of {options}") - return config + return True -def strip_blank_recursive(nested_list: list): +# --------------------------------------------------------------------------- +# Utilities for getting configuration content +# --------------------------------------------------------------------------- +def get_logger_config(caller_file_path: Union[str, Path], name: str=None) -> dict: """ - Strip blank space or newline characters recursively for a nested list + Get logger config as dictionary - *This updates the original list passed in* + :param caller_file_path: file path of the caller, __file__ + :param name: the name(section in logme.ini) of the config to be passed. (optional, default: 'logme') + :return: logger configuration dictionary + :raises: InvalidConfig, if name is not in config, or name == 'colors' """ - if not isinstance(nested_list, list): - raise ValueError(f"iterable passed must be type of list. not '{type(nested_list).__name__}'") + if name == 'colors': + raise InvalidLoggerConfig(f"'colors' cannot be used as a logger configuration") - for i, v in enumerate(nested_list): - if isinstance(v, list): - strip_blank_recursive(v) - elif isinstance(v, str): - val_ = str_eval(v) + if not name: + name = 'logme' - nested_list[i] = val_ + try: + return get_config_content(caller_file_path, name=name) + except NoSectionError: + raise InvalidLoggerConfig(f"Invalid logger config '{name}'") -def str_eval(parse_str: str): +def get_color_config(caller_file_path: Union[str, Path]): """ - Evaluate string, return the respective object if evaluation is successful, + Return color configuration dict if 'colors' section exists, return None if not found """ try: - val = ast.literal_eval(parse_str.strip()) - except (ValueError, SyntaxError): # SyntaxError raised when passing in "{asctime}::{message}" - val = parse_str.strip() + return get_config_content(caller_file_path, 'colors') + except NoSectionError: + return - return val - -def ensure_dir(dir_path: Union[Path, str], path_type: str='parent'): +def get_config_content(caller_file_path: Union[str, Path], name: str) -> dict: """ - Ensure the existence of the directory, - - :param dir_path: the path to be ensured - :param path_type: current - ensure the passed in directory exists, typically used when + Get the config section as a dictionary + :param caller_file_path: file path of the caller, __file__ + :param name: the section name in an .ini file + :return: configuration as dict """ - path_mapping = { - 'current': Path(dir_path).resolve(), - 'parent': Path(dir_path).parent.resolve() - } - try: - dir_abs = path_mapping[path_type] + init_file_path = get_ini_file_path(caller_file_path) - if not dir_abs.exists(): - dir_abs.mkdir(parents=True, exist_ok=True) - except KeyError: - raise InvalidOption(f"{path_type} is not a valid option, please pass in either 'parent' or 'current'") + config = ConfigParser.from_files(init_file_path) + try: + return config.to_dict(section=name) + except NoSectionError: + raise NoSectionError(f"'{name}' is not a valid configuration in {init_file_path}") -def check_scope(scope: str, options: list) -> bool: - """ - check if the scope passed is within the options. - Used both in logme/__init__.py and providers.LogDecorator +def get_ini_file_path(caller_file_path: Union[str, Path]) -> Path: """ - if scope not in options: - raise LogmeError(f"scope '{scope}' is not supported, " - f"please use one of {options}") - - return True + Get the logme.ini config file path + :param caller_file_path: file path of the caller, callable.__file__ -@contextmanager -def cd(dir_path: str): + :return: Path object of the logme.ini """ - Context manager for cd, change back to original directory when done - """ - cwd = os.getcwd() - try: - os.chdir(os.path.expanduser(dir_path)) - yield - finally: - os.chdir(cwd) + conf_path = Path(caller_file_path).parent / 'logme.ini' + + if caller_file_path in [Path(Path(caller_file_path).root).resolve(), + Path(caller_file_path).home().resolve()]: + raise ValueError(f"logme.ini does not exist, please use 'logme init' command in your project root.") + + if not conf_path.exists(): + return get_ini_file_path(Path(caller_file_path).parent) + else: + return conf_path.resolve() diff --git a/requirements.txt b/requirements.txt index f9db913..27731f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ click +bnmutils pytest codecov Pygments diff --git a/setup.py b/setup.py index 9f1a520..14a9e7e 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ requires = [ 'click', + 'bnmutils', ] # Install colorama on windows systems as an optional dependency diff --git a/tests/conftest.py b/tests/conftest.py index 010edf1..e9b03d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ from click.testing import CliRunner from logme.cli import cli -from logme.config import get_logger_config, get_color_config +from logme.utils import get_logger_config, get_color_config from logme.providers import LogmeLogger diff --git a/tests/test_cli_package/test_cli.py b/tests/test_cli_package/test_cli.py index 63c88e6..4fd37f7 100644 --- a/tests/test_cli_package/test_cli.py +++ b/tests/test_cli_package/test_cli.py @@ -1,13 +1,14 @@ import pytest -from configparser import ConfigParser import shutil from pathlib import Path from click.testing import CliRunner +from bnmutils import ConfigParser +from bnmutils.novelty import cd + from logme.exceptions import LogmeError -from logme.utils import cd, conf_item_to_dict -from logme.config import read_config, get_logger_config +from logme.utils import get_logger_config from logme import __version__ from logme import cli @@ -41,8 +42,7 @@ def test_init(self, tmpdir, file_path, cmd_args): assert result.exit_code == 0 assert expected_file.is_file() - conf = ConfigParser() - conf.read(expected_file) + conf = ConfigParser.from_files(expected_file) assert conf.sections() == ['colors', 'logme'] # Assert the first section is the color config @@ -75,7 +75,7 @@ def test_init_file_change(self, tmpdir, option, key, expected): self.runner.invoke(cli, ['init', '-p', tmpdir] + option) - conf = read_config(tmpdir.join('logme.ini')) + conf = ConfigParser.from_files(tmpdir.join('logme.ini')) assert conf.get(*key) == expected @@ -86,12 +86,12 @@ def test_init_chained_options(self, tmpdir): self.runner.invoke(cli, ['init', '-p', tmp, '-mk', '-lp', tmp.join('var/log/dummy.log')]) - conf = read_config(tmp.join('logme.ini')) + config = ConfigParser.from_files(tmp.join('logme.ini')) - fh_conf = conf.get('logme', 'file') + fh_conf = config.to_dict(section='logme', option='file') - assert conf_item_to_dict(fh_conf)['filename'] == tmp.join('var/log/dummy.log') - assert set(conf_item_to_dict(fh_conf).keys()) == {'active', 'level', 'filename', 'type'} + assert fh_conf['filename'] == tmp.join('var/log/dummy.log') + assert set(fh_conf.keys()) == {'active', 'level', 'filename', 'type'} def test_init_raise_invalid_dir(self, tmpdir): @@ -142,7 +142,7 @@ def test_add_command(self, tmpdir): result = self.runner.invoke(cli, ['add', 'blah']) config_path = tmpdir.join('logme.ini') - config = read_config(config_path) + config = ConfigParser.from_files(config_path) assert result.exit_code == 0 assert Path(config_path).is_file() @@ -167,12 +167,12 @@ def test_remove_command(self, tmpdir): self.runner.invoke(cli, ['add', 'test']) config_path = tmpdir.join('logme.ini') - config_before = read_config(config_path) + config_before = ConfigParser.from_files(config_path) assert set(config_before.sections()) == {'colors', 'logme', 'test'} result = self.runner.invoke(cli, ['remove', 'test']) - config_after = read_config(config_path) + config_after = ConfigParser.from_files(config_path) assert result.exit_code == 0 assert config_after.sections() == ['colors', 'logme'] diff --git a/tests/test_cli_package/test_cli_utils.py b/tests/test_cli_package/test_cli_utils.py index 2b5cac2..5443820 100644 --- a/tests/test_cli_package/test_cli_utils.py +++ b/tests/test_cli_package/test_cli_utils.py @@ -3,9 +3,10 @@ from pathlib import Path from click.testing import CliRunner +from bnmutils.novelty import cd + from logme import cli from logme.exceptions import LogmeError -from logme.utils import cd from logme.cli._cli_utils import (ensure_conf_exist, validate_conf, get_tpl, map_template, check_options, get_color_tpl) @@ -59,7 +60,7 @@ def test_validate_conf_none_valid(tmpdir): def test_get_color_tpl(): expected = { 'colors': { - 'CRITICAL': '\ncolor: PURPLE\nstyle: BOLD', + 'CRITICAL': {'color': 'PURPLE', 'style': 'BOLD'}, 'ERROR': 'RED', 'WARNING': 'YELLOW', 'INFO': 'None', @@ -80,7 +81,7 @@ def test_get_tpl(tmpdir): assert set(template['test_log'].keys()) == {'level', 'formatter', 'stream', 'file', 'null'} assert template['test_log']['level'] == 'INFO' - assert str(log_path) in template['test_log']['file'] + assert template['test_log']['file']['filename'] == log_path assert Path(log_path).parent.exists() diff --git a/tests/test_cli_package/test_upgrade_utils.py b/tests/test_cli_package/test_upgrade_utils.py index 62ca8db..299b47e 100644 --- a/tests/test_cli_package/test_upgrade_utils.py +++ b/tests/test_cli_package/test_upgrade_utils.py @@ -3,9 +3,9 @@ import shutil from pathlib import Path +from bnmutils import ConfigParser + from logme.cli._upgrade_utils import _upgrade_logging_config_section, upgrade_to_latest -from logme.config import read_config -from logme.utils import conf_to_dict from logme.cli._upgrade_utils import NONLOGGER_CONFIGS from .config_template import ver10_config, ver11_config @@ -23,21 +23,21 @@ def test_upgrade_to_latest(tmpdir): upgrade_to_latest(tmp_logme) # Validate upgraded file - config = read_config(tmp_logme) - config_before = read_config(logme_file) + config = ConfigParser.from_files(tmp_logme) + config_before = ConfigParser.from_files(logme_file) assert set(config.sections()) == set(NONLOGGER_CONFIGS + config_before.sections()) # Validate the latest version has not been changed - assert conf_to_dict(config.items('latest')) == \ - conf_to_dict(config_before.items('latest')) == \ + assert config.to_dict(section='latest') == \ + config_before.to_dict(section='latest') == \ ver11_config for i in config.sections(): if i == 'colors': continue - conf_dict = conf_to_dict(config.items(i)) + conf_dict = config.to_dict(section=i) for k, v in conf_dict.items(): if isinstance(v, dict): @@ -54,8 +54,8 @@ def test_upgrade_colors_config_not_changed(tmpdir): upgrade_to_latest(tmp_logme) - config_before = read_config(local_logme_file) - config_after = read_config(tmp_logme) + config_before = ConfigParser.from_files(local_logme_file) + config_after = ConfigParser.from_files(tmp_logme) assert config_before.items('colors') == config_after.items('colors') diff --git a/tests/test_color_provider.py b/tests/test_color_provider.py index b2173b1..cc04ea9 100644 --- a/tests/test_color_provider.py +++ b/tests/test_color_provider.py @@ -3,7 +3,7 @@ import logging from logme.color_provider import Color, ColorFormatter -from logme.config import get_color_config, get_config_content +from logme.utils import get_color_config, get_config_content from logme.exceptions import InvalidColorConfig @@ -42,18 +42,24 @@ def test_color_code_reset(): == str(reset_with_kwargs2) == str(reset_bg) -@pytest.mark.parametrize('parse_args', +@pytest.mark.parametrize('parse_args, msg', [ - pytest.param({'color': 'red', 'style': 'blah'}, id='invalid style passed'), - pytest.param('bold', id='Passing in style as color'), + pytest.param({'color': 'red', 'style': 'blah'}, + "'blah' is not a valid style or background color", + id='invalid style passed'), + pytest.param('bold', + "'bold' is not a valid color", + id='Passing in style as color'), ]) -def test_color_code_raise(parse_args): - with pytest.raises(InvalidColorConfig): +def test_color_code_raise(parse_args, msg): + with pytest.raises(InvalidColorConfig) as e_info: if isinstance(parse_args, dict): Color(**parse_args) else: Color(parse_args) + assert e_info.value.args[0] == msg + def test_color_repr(capsys): color = Color(color='purple') diff --git a/tests/test_config.py b/tests/test_config.py deleted file mode 100644 index a0e00ef..0000000 --- a/tests/test_config.py +++ /dev/null @@ -1,151 +0,0 @@ -import pytest - -from pathlib import Path - -from logme.config import (get_logger_config, read_config, get_ini_file_path, - get_config_content, get_color_config) -from logme.utils import dict_to_config, flatten_config_dict, cd -from logme.exceptions import InvalidConfig - - -@pytest.mark.parametrize('conf_name, expected_master_level', - [ - pytest.param(None, 'DEBUG', id='when no conf_name is being passed'), - pytest.param('my_test_logger', 'INFO', id='when conf_name is being passed') - ]) -def test_get_logger_config(conf_name, expected_master_level): - logger_config = get_logger_config(__file__, conf_name) - - assert logger_config['level'] == expected_master_level - - -@pytest.mark.parametrize('conf_name', - [ - pytest.param('colors', id='colors passed as configuration'), - pytest.param('bunny', id='none existent config section') - ]) -def test_get_logger_config_raise(conf_name): - with pytest.raises(InvalidConfig): - get_logger_config(__file__, conf_name) - - -def test_get_color_config(): - expected = { - 'CRITICAL': {'bg': 'red', 'color': 'white', 'style': 'Bold'}, - 'ERROR': 'PURPLE', - 'WARNING': 'YELLOW', - 'INFO': 'GREEN', - 'DEBUG': 'WHITE' - } - color_conf = get_color_config(__file__) - assert expected == color_conf - - -def test_color_config_with_none_value(): - """ Test for correct output when a value is None""" - expected = { - 'CRITICAL': {'color': 'PURPLE', 'style': 'Bold'}, - 'ERROR': 'RED', - 'WARNING': 'YELLOW', - 'INFO': None, - 'DEBUG': 'GREEN', - } - color_config = get_config_content(__file__, 'colors_test2') - - assert color_config == expected - - -def test_color_config_none_exist(tmpdir): - """ - Ensure color config returns None if none existent - """ - logme_file = tmpdir.join('logme.ini') - - config_dict = {'logme': flatten_config_dict(get_logger_config(__file__))} - config = dict_to_config(config_dict) - - with open(logme_file, 'w') as file: - config.write(file) - - color_config = get_color_config(logme_file) - - assert color_config is None - - -def test_get_config_content(): - conf_content = get_config_content(__file__, 'my_test_logger') - - assert type(conf_content) == dict - assert type(conf_content['FileHandler']) == dict - assert conf_content['level'] == 'INFO' - - -def test_get_config_content_ver11(): - """ - Added for v1.1.0, ensure get_config_content() works with both v1.1.* and v1.0.* - """ - expected = {'level': 'DEBUG', - 'formatter': '{asctime} - {name} - {levelname} - {message}', - 'stream': { - 'type': 'StreamHandler', - 'active': True, - 'level': 'INFO'}, - 'file': { - 'type': 'FileHandler', - 'active': False, - 'level': 'DEBUG', - 'filename': 'mylogpath/foo.log'}, - 'null': - { - 'type': 'NullHandler', - 'active': False, - 'level': 'DEBUG'} - } - conf_content = get_config_content(__file__, 'ver11_config') - - assert conf_content == expected - - -def test_get_config_content_raise(): - with pytest.raises(InvalidConfig): - get_config_content(__file__, 'blah') - - -def test_read_config(tmpdir_class_scope): - project_dir, _ = tmpdir_class_scope - - config = read_config(Path(project_dir) / 'logme.ini') - - assert config.sections() == ['colors', 'logme'] - - -def test_read_config_empty(tmpdir): - file = tmpdir.join('foo.ini') - file.open('w').close() - - assert Path(file).exists() - - with pytest.raises(InvalidConfig): - read_config(file) - - -def test_read_config_file_non_exist(): - - with pytest.raises(InvalidConfig): - read_config('blah.ini') - - -def test_get_ini_file_path(): - conf_path = get_ini_file_path(__file__) - - assert conf_path == Path(__file__).parent / 'logme.ini' - - -def test_get_ini_file_path_raise(tmpdir, monkeypatch): - monkeypatch.setattr('pathlib.Path.root', tmpdir) - - target_dir = tmpdir.mkdir('test').mkdir('test_again') - with pytest.raises(ValueError): - get_ini_file_path(target_dir) - - diff --git a/tests/test_providers_logger_object.py b/tests/test_providers_logger_object.py index 3ea4152..8a8eca0 100644 --- a/tests/test_providers_logger_object.py +++ b/tests/test_providers_logger_object.py @@ -4,7 +4,7 @@ from pathlib import Path from logme.providers import LogmeLogger -from logme.config import get_logger_config, get_color_config, get_config_content +from logme.utils import get_logger_config, get_color_config, get_config_content from logme.exceptions import DuplicatedHandler, InvalidOption, LogmeError diff --git a/tests/test_providers_logprovider.py b/tests/test_providers_logprovider.py index 1a88ada..44c633a 100644 --- a/tests/test_providers_logprovider.py +++ b/tests/test_providers_logprovider.py @@ -1,6 +1,6 @@ import pytest -from logme.config import get_logger_config +from logme.utils import get_logger_config from logme.providers import LogProvider, ModuleLogger, LogmeLogger diff --git a/tests/test_utils.py b/tests/test_utils.py index e5a13d2..f1fc0f3 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,197 +2,152 @@ from pathlib import Path -from configparser import ConfigParser - -from logme.exceptions import InvalidOption -from logme.utils import (flatten_config_dict, conf_to_dict, dict_to_config, str_eval, - ensure_dir, check_scope, conf_item_to_dict, strip_blank_recursive, cd) - - -class TestPackageUtils: - - @classmethod - def setup(cls): - - cls.sample_dict = { - "level": "DEBUG", - "format": "%(levelname)s: %(message)s", - "StreamHandler": { - "active": True, - "level": "DEBUG", - }, - "FileHandler": { - "level": "DEBUG", - "filename": "/var/log/mylog.log", - }, - } - - cls.sample_conf_dict = { - "level": "DEBUG", - "format": "%(levelname)s: %(message)s", - "StreamHandler": "\nactive: True\nlevel: DEBUG", - "FileHandler": f"\nlevel: DEBUG" - f"\nfilename: /var/log/mylog.log", - } - - cls.conf_section = [(k, v) for k, v in cls.sample_conf_dict.items()] - - def test_conf_section_to_dict(self): - output = conf_to_dict(self.conf_section) - - assert output == self.sample_dict - - @pytest.mark.parametrize('parse_option', - [pytest.param(" \ntype: option \n second_val : my_val ", - id='config options with blank space in the beginning and middle'), - pytest.param("\n type : option \n second_val : my_val \n", - id='config option with blank space after new line, ' - 'and new line after last option'), - pytest.param("\ntype: option \nsecond_val: my_val ", - id='config option with no blank space')]) - def test_conf_item_to_dict(self, parse_option): - expected_dict = {'type': 'option', - 'second_val': 'my_val'} - output = conf_item_to_dict(parse_option) - - assert expected_dict == output - - def test_conf_item_to_dict_multiple_colons(self): - parse_item = '\nactive: True' \ - '\nlevel: INFO' \ - '\nformatter: {funcName} :: {levelname} :: {message}' - - expected = { - 'active': True, - 'level': 'INFO', - 'formatter': '{funcName} :: {levelname} :: {message}', - } - - assert conf_item_to_dict(parse_item) == expected - - @pytest.mark.parametrize('parse_option', - [pytest.param(['blah', 'test'], id='when option passed is a list'), - pytest.param(20, id='when option passed is an int'), - pytest.param({'blah': 'test'}, id='when option passed is a dict'), - pytest.param("\nhello\nbye", id='when option passed is in invalid format'), - pytest.param("\nhello: hi\nbye", id='when option passed is in invalid format'), - ]) - def test_conf_item_to_dict_raise(self, parse_option): - with pytest.raises(InvalidOption): - conf_item_to_dict(parse_option) - - def test_flatten_config_dict(self): - flattened = flatten_config_dict(self.sample_dict) - - assert flattened == self.sample_conf_dict - - @pytest.mark.parametrize('parse_dict', - [pytest.param({'test': 'test1', 'int_val': 1}, - id='when one of the dict value is integer'), - pytest.param({'test': 'test1', 'list_val': [1, 2, 3, 4, 5]}, - id='when one of the dict value is list') - ]) - def test_flatten_config_dict_raise(self, parse_dict): - with pytest.raises(ValueError): - flatten_config_dict(parse_dict) - - def test_dict_to_config(self): - - conf_content = {'test': {'hello': 1}} - config = dict_to_config(conf_content) - - assert type(config) == ConfigParser - assert config.sections() == ['test'] - - def test_dict_to_config_option_not_str(self): - content = { - 'test': { - 'level': 'DEBUG', - 'formatter': None, +from bnmutils import ConfigParser +from configparser import NoSectionError + +from logme.exceptions import InvalidOption, InvalidLoggerConfig +from logme.utils import (get_logger_config, get_ini_file_path, get_config_content, + get_color_config, ensure_dir, check_scope) + + +@pytest.mark.parametrize('subpath, path_type, expected_path', + [pytest.param('test/my_test_dir', 'current', 'test/my_test_dir', + id='make sure the exact dir exists'), + pytest.param('foo/my_dir/myfile.txt', 'parent', 'foo/my_dir', + id='make sure the parent dir exists')]) +def test_ensure_dir(tmpdir, subpath, path_type, expected_path): + dir_path = Path(tmpdir) / Path(subpath) + + ensure_dir(dir_path, path_type=path_type) + + assert (Path(tmpdir) / Path(expected_path)).exists() + + +def test_ensure_dir_raise(tmpdir): + with pytest.raises(InvalidOption): + ensure_dir(tmpdir, path_type='cwd') + + +@pytest.mark.parametrize('scope, options', [pytest.param('function', ['function', 'class']), + pytest.param('class', ['function', 'class', 'module', 'blah'])]) +def test_check_scope_function(scope, options): + assert check_scope(scope, options) is True + + +@pytest.mark.parametrize('conf_name, expected_master_level', + [ + pytest.param(None, 'DEBUG', id='when no conf_name is being passed'), + pytest.param('my_test_logger', 'INFO', id='when conf_name is being passed') + ]) +def test_get_logger_config(conf_name, expected_master_level): + logger_config = get_logger_config(__file__, conf_name) + + assert logger_config['level'] == expected_master_level + + +@pytest.mark.parametrize('conf_name', + [ + pytest.param('colors', id='colors passed as configuration'), + pytest.param('bunny', id='none existent config section') + ]) +def test_get_logger_config_raise(conf_name): + with pytest.raises(InvalidLoggerConfig): + get_logger_config(__file__, conf_name) + + +def test_get_color_config(): + expected = { + 'CRITICAL': {'bg': 'red', 'color': 'white', 'style': 'Bold'}, + 'ERROR': 'PURPLE', + 'WARNING': 'YELLOW', + 'INFO': 'GREEN', + 'DEBUG': 'WHITE' + } + color_conf = get_color_config(__file__) + assert expected == color_conf + + +def test_color_config_with_none_value(): + """ Test for correct output when a value is None""" + expected = { + 'CRITICAL': {'color': 'PURPLE', 'style': 'Bold'}, + 'ERROR': 'RED', + 'WARNING': 'YELLOW', + 'INFO': None, + 'DEBUG': 'GREEN', + } + color_config = get_config_content(__file__, 'colors_test2') + + assert color_config == expected + + +def test_color_config_none_exist(tmpdir): + """ + Ensure color config returns None if none existent + """ + logme_file = tmpdir.join('logme.ini') + + logger_conifg = get_logger_config(__file__) + config_dict = {'logme': logger_conifg} + + config = ConfigParser.from_dict(config_dict) + + with open(logme_file, 'w') as file: + config.write(file) + + color_config = get_color_config(logme_file) + + assert color_config is None + + +def test_get_config_content(): + conf_content = get_config_content(__file__, 'my_test_logger') + + assert type(conf_content) == dict + assert type(conf_content['FileHandler']) == dict + assert conf_content['level'] == 'INFO' + + +def test_get_config_content_ver11(): + """ + Added for v1.1.0, ensure get_config_content() works with both v1.1.* and v1.0.* + """ + expected = {'level': 'DEBUG', + 'formatter': '{asctime} - {name} - {levelname} - {message}', 'stream': { 'type': 'StreamHandler', 'active': True, - 'level': 'DEBUG', - }, + 'level': 'INFO'}, 'file': { 'type': 'FileHandler', 'active': False, 'level': 'DEBUG', - 'filename': 'mylogpath/foo.log', - }, - 'null': { - 'type': 'NullHandler', - 'active': False, - 'level': 'NOTSET' - }, - } - } - with pytest.raises(TypeError): - dict_to_config(content) - - @pytest.mark.parametrize('iterable_, expected', - [pytest.param(['hello ', '\nhi ', 1], ['hello', 'hi', 1], - id='when the iterable passed is not nested'), - pytest.param([[' hello ', 'hi \n'], [1, ' blah ', ' bye']], - [['hello', 'hi'], [1, 'blah', 'bye']], - id='when the iterable passed has nested list'), - pytest.param([[' hello ', 'hi \n'], [1, ' blah ', ' bye'], 'greet '], - [['hello', 'hi'], [1, 'blah', 'bye'], 'greet'], - id='when a tuple is passed and it has nested list and tuple'), - ]) - def test_strip_blank_recursive(self, iterable_, expected): - strip_blank_recursive(iterable_) - - assert iterable_ == expected - - @pytest.mark.parametrize('parse_arg', [pytest.param(1, id='int value passed'), - pytest.param('hello foo bar', id='string value passed'), - pytest.param(('hello', 'hi'), id='tuple value passed') - ]) - def test_strip_blank_recursive_raise(self, parse_arg): - with pytest.raises(ValueError): - strip_blank_recursive(parse_arg) - - @pytest.mark.parametrize('parse_str, expected', - [ - pytest.param('None', None, id='Passing a None value'), - pytest.param('False', False, id='Passing a boolean value'), - pytest.param('Hello world', 'Hello world', id='Passing a regular string'), - pytest.param('[1, 2, 3]', [1,2,3], id='Passing a list value'), - ]) - def test_str_eval(self, parse_str, expected): - result = str_eval(parse_str) - - assert result == expected - - @pytest.mark.parametrize('subpath, path_type, expected_path', - [pytest.param('test/my_test_dir', 'current', 'test/my_test_dir', - id='make sure the exact dir exists'), - pytest.param('foo/my_dir/myfile.txt', 'parent', 'foo/my_dir', - id='make sure the parent dir exists')]) - def test_ensure_dir(self, tmpdir, subpath, path_type, expected_path): - dir_path = Path(tmpdir) / Path(subpath) - - ensure_dir(dir_path, path_type=path_type) - - assert (Path(tmpdir) / Path(expected_path)).exists() - - def test_ensure_dir_raise(self, tmpdir): - with pytest.raises(InvalidOption): - ensure_dir(tmpdir, path_type='cwd') - - @pytest.mark.parametrize('scope, options', [pytest.param('function', ['function', 'class']), - pytest.param('class', ['function', 'class', 'module', 'blah'])]) - def test_check_scope_function(self, scope, options): - assert check_scope(scope, options) is True - - def test_cd(self, tmpdir): - - original_cwd = Path.cwd() - - with cd(tmpdir): - assert Path.cwd() == tmpdir - assert Path.cwd() != original_cwd - - assert original_cwd == Path.cwd() + 'filename': 'mylogpath/foo.log'}, + 'null': + { + 'type': 'NullHandler', + 'active': False, + 'level': 'DEBUG'} + } + conf_content = get_config_content(__file__, 'ver11_config') + + assert conf_content == expected + + +def test_get_config_content_raise(): + with pytest.raises(NoSectionError): + get_config_content(__file__, 'blah') + + +def test_get_ini_file_path(): + conf_path = get_ini_file_path(__file__) + + assert conf_path == Path(__file__).parent / 'logme.ini' + + +def test_get_ini_file_path_raise(tmpdir, monkeypatch): + monkeypatch.setattr('pathlib.Path.root', tmpdir) + target_dir = tmpdir.mkdir('test').mkdir('test_again') + with pytest.raises(ValueError): + get_ini_file_path(target_dir)