From d1d19e3d06b6ea9f3e0e4967ce5f79b05729adcd Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 15 Dec 2023 06:51:48 -0600 Subject: [PATCH] Added indented logger to improve console output --- bumpversion/bump.py | 16 ++- bumpversion/cli.py | 6 +- bumpversion/config/__init__.py | 9 +- bumpversion/config/files.py | 14 +-- bumpversion/config/files_legacy.py | 5 +- bumpversion/indented_logger.py | 7 ++ bumpversion/scm.py | 6 +- bumpversion/version_part.py | 15 ++- tests/test_indented_logger.py | 156 +++++++++++++++++------------ tests/test_version_part.py | 2 +- 10 files changed, 148 insertions(+), 88 deletions(-) diff --git a/bumpversion/bump.py b/bumpversion/bump.py index a36d381b..62fe2c13 100644 --- a/bumpversion/bump.py +++ b/bumpversion/bump.py @@ -1,5 +1,4 @@ """Version changing methods.""" -import logging import shlex from pathlib import Path from typing import TYPE_CHECKING, ChainMap, List, Optional @@ -12,9 +11,10 @@ from bumpversion.config.files import update_config_file from bumpversion.config.files_legacy import update_ini_config_file from bumpversion.exceptions import ConfigurationError +from bumpversion.ui import get_indented_logger from bumpversion.utils import get_context, key_val_string -logger = logging.getLogger("bumpversion") +logger = get_indented_logger(__name__) def get_next_version( @@ -36,14 +36,18 @@ def get_next_version( ConfigurationError: If it can't generate the next version. """ if new_version: + logger.info("Attempting to set new version '%s'", new_version) + logger.indent() next_version = config.version_config.parse(new_version) elif version_part: logger.info("Attempting to increment part '%s'", version_part) + logger.indent() next_version = current_version.bump(version_part, config.version_config.order) else: raise ConfigurationError("Unable to get the next version.") logger.info("Values are now: %s", key_val_string(next_version.values)) + logger.dedent() return next_version @@ -66,8 +70,13 @@ def do_bump( """ from bumpversion.files import modify_files, resolve_file_config + logger.indent() + ctx = get_context(config) + logger.info("Parsing current version '%s'", config.current_version) + logger.indent() version = config.version_config.parse(config.current_version) + logger.dedent() next_version = get_next_version(version, config, version_part, new_version) next_version_str = config.version_config.serialize(next_version, ctx) logger.info("New version will be '%s'", next_version_str) @@ -76,6 +85,8 @@ def do_bump( logger.info("Version is already '%s'", next_version_str) return + logger.dedent() + if dry_run: logger.info("Dry run active, won't touch any files.") @@ -91,6 +102,7 @@ def do_bump( ctx = get_context(config, version, next_version) ctx["new_version"] = next_version_str commit_and_tag(config, config_file, configured_files, ctx, dry_run) + logger.info("Done.") def commit_and_tag( diff --git a/bumpversion/cli.py b/bumpversion/cli.py index 6a82c5b2..7b9cc86e 100644 --- a/bumpversion/cli.py +++ b/bumpversion/cli.py @@ -1,5 +1,4 @@ """bump-my-version Command line interface.""" -import logging from typing import List, Optional import rich_click as click @@ -12,10 +11,10 @@ from bumpversion.config.files import find_config_file from bumpversion.files import ConfiguredFile, modify_files from bumpversion.show import do_show, log_list -from bumpversion.ui import print_warning, setup_logging +from bumpversion.ui import get_indented_logger, print_warning, setup_logging from bumpversion.utils import get_context, get_overrides -logger = logging.getLogger(__name__) +logger = get_indented_logger(__name__) @click.group( @@ -307,6 +306,7 @@ def bump( config.add_files(files) config.included_paths = files + logger.dedent() do_bump(version_part, new_version, config, found_config_file, dry_run) diff --git a/bumpversion/config/__init__.py b/bumpversion/config/__init__.py index 21cbda1d..12de9b7e 100644 --- a/bumpversion/config/__init__.py +++ b/bumpversion/config/__init__.py @@ -1,17 +1,17 @@ """Configuration management.""" from __future__ import annotations -import logging from typing import TYPE_CHECKING, Union from bumpversion.config.files import read_config_file from bumpversion.config.models import Config from bumpversion.exceptions import ConfigurationError +from bumpversion.ui import get_indented_logger if TYPE_CHECKING: # pragma: no-coverage from pathlib import Path -logger = logging.getLogger(__name__) +logger = get_indented_logger(__name__) DEFAULTS = { "current_version": None, @@ -49,6 +49,9 @@ def get_configuration(config_file: Union[str, Path, None] = None, **overrides) - from bumpversion.config.utils import get_all_file_configs, get_all_part_configs from bumpversion.scm import SCMInfo, SourceCodeManager, get_scm_info # noqa: F401 + logger.info("Reading configuration") + logger.indent() + config_dict = DEFAULTS.copy() parsed_config = read_config_file(config_file) if config_file else {} @@ -75,6 +78,8 @@ def get_configuration(config_file: Union[str, Path, None] = None, **overrides) - # Update and verify the current_version config.current_version = check_current_version(config) + logger.dedent() + return config diff --git a/bumpversion/config/files.py b/bumpversion/config/files.py index 49c523c7..c8287d7a 100644 --- a/bumpversion/config/files.py +++ b/bumpversion/config/files.py @@ -2,18 +2,17 @@ from __future__ import annotations -import logging from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, MutableMapping, Union from bumpversion.config.files_legacy import read_ini_file -from bumpversion.ui import print_warning +from bumpversion.ui import get_indented_logger, print_warning if TYPE_CHECKING: # pragma: no-coverage from bumpversion.config.models import Config from bumpversion.version_part import Version -logger = logging.getLogger(__name__) +logger = get_indented_logger(__name__) CONFIG_FILE_SEARCH_ORDER = ( ".bumpversion.cfg", @@ -67,7 +66,7 @@ def read_config_file(config_file: Union[str, Path, None] = None) -> Dict[str, An logger.info("Configuration file not found: %s.", config_path) return {} - logger.info("Reading config file %s:", config_file) + logger.info("Reading config file: %s", config_file) if config_path.suffix == ".cfg": print_warning("The .cfg file format is deprecated. Please use .toml instead.") @@ -120,9 +119,11 @@ def update_config_file( from bumpversion.files import DataFileUpdater if not config_file: - logger.info("No configuration file found to update.") + logger.info("\n%sNo configuration file found to update.", logger.indent_str) return - + else: + logger.info("\n%sProcessing config file: %s", logger.indent_str, config_file) + logger.indent() config_path = Path(config_file) if config_path.suffix != ".toml": logger.info("You must have a `.toml` suffix to update the config file: %s.", config_path) @@ -142,3 +143,4 @@ def update_config_file( updater = DataFileUpdater(datafile_config, config.version_config.part_configs) updater.update_file(current_version, new_version, context, dry_run) + logger.dedent() diff --git a/bumpversion/config/files_legacy.py b/bumpversion/config/files_legacy.py index 6a69b113..7dcaba91 100644 --- a/bumpversion/config/files_legacy.py +++ b/bumpversion/config/files_legacy.py @@ -1,13 +1,14 @@ """This module handles the legacy config file format.""" from __future__ import annotations -import logging import re from difflib import context_diff from pathlib import Path from typing import Any, Dict, Union -logger = logging.getLogger(__name__) +from bumpversion.ui import get_indented_logger + +logger = get_indented_logger(__name__) def read_ini_file(file_path: Path) -> Dict[str, Any]: # noqa: C901 diff --git a/bumpversion/indented_logger.py b/bumpversion/indented_logger.py index 3ff1b281..a8b8faad 100644 --- a/bumpversion/indented_logger.py +++ b/bumpversion/indented_logger.py @@ -32,6 +32,13 @@ def __init__( if reset: self.reset() + @property + def current_indent(self) -> int: + """ + The current indent level. + """ + return CURRENT_INDENT.get() + def indent(self, amount: int = 1) -> None: """ Increase the indent level by `amount`. diff --git a/bumpversion/scm.py b/bumpversion/scm.py index 75240b2f..43f1e16c 100644 --- a/bumpversion/scm.py +++ b/bumpversion/scm.py @@ -1,6 +1,5 @@ """Version control system management.""" -import logging import os import re import subprocess @@ -9,6 +8,7 @@ from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, ClassVar, List, MutableMapping, Optional, Type, Union +from bumpversion.ui import get_indented_logger from bumpversion.utils import extract_regex_flags if TYPE_CHECKING: # pragma: no-coverage @@ -16,7 +16,7 @@ from bumpversion.exceptions import DirtyWorkingDirectoryError, SignedTagsError -logger = logging.getLogger(__name__) +logger = get_indented_logger(__name__) @dataclass @@ -145,6 +145,7 @@ def commit_to_scm( "Preparing" if do_commit else "Would prepare", cls.__name__, ) + logger.indent() for path in files: logger.info( "%s changes in file '%s' to %s", @@ -171,6 +172,7 @@ def commit_to_scm( new_version=context["new_version"], extra_args=extra_args, ) + logger.dedent() @classmethod def tag_in_scm(cls, config: "Config", context: MutableMapping, dry_run: bool = False) -> None: diff --git a/bumpversion/version_part.py b/bumpversion/version_part.py index 41c2d054..038c0fcb 100644 --- a/bumpversion/version_part.py +++ b/bumpversion/version_part.py @@ -1,5 +1,4 @@ """Module for managing Versions and their internal parts.""" -import logging import re import string from copy import copy @@ -10,9 +9,10 @@ from bumpversion.config.models import VersionPartConfig from bumpversion.exceptions import FormattingError, InvalidVersionPartError, MissingValueError from bumpversion.functions import NumericFunction, PartFunction, ValuesFunction +from bumpversion.ui import get_indented_logger from bumpversion.utils import key_val_string, labels_for_format -logger = logging.getLogger(__name__) +logger = get_indented_logger(__name__) class VersionPart: @@ -183,6 +183,7 @@ def parse(self, version_string: Optional[str] = None) -> Optional[Version]: version_string, regexp_one_line, ) + logger.indent() match = self.parse_regex.search(version_string) @@ -202,6 +203,7 @@ def parse(self, version_string: Optional[str] = None) -> Optional[Version]: v = Version(_parsed, version_string) logger.info("Parsed the following values: %s", key_val_string(v.values)) + logger.dedent() return v @@ -268,8 +270,8 @@ def _serialize( def _choose_serialize_format(self, version: Version, context: MutableMapping) -> str: chosen = None - logger.debug("Available serialization formats: '%s'", "', '".join(self.serialize_formats)) - + logger.debug("Evaluating serialization formats") + logger.indent() for serialize_format in self.serialize_formats: try: self._serialize(version, serialize_format, context, raise_if_incomplete=True) @@ -291,7 +293,7 @@ def _choose_serialize_format(self, version: Version, context: MutableMapping) -> if not chosen: raise KeyError("Did not find suitable serialization format") - + logger.dedent() logger.debug("Selected serialization format '%s'", chosen) return chosen @@ -307,6 +309,9 @@ def serialize(self, version: Version, context: MutableMapping) -> str: Returns: The serialized version as a string """ + logger.debug("Serializing version '%s'", version) + logger.indent() serialized = self._serialize(version, self._choose_serialize_format(version, context), context) logger.debug("Serialized to '%s'", serialized) + logger.dedent() return serialized diff --git a/tests/test_indented_logger.py b/tests/test_indented_logger.py index 404e37c5..ba87f3f5 100644 --- a/tests/test_indented_logger.py +++ b/tests/test_indented_logger.py @@ -4,68 +4,94 @@ import logging -class TestIndentedLogger: - def test_does_not_indent_without_intent(self, caplog: pytest.LogCaptureFixture): - caplog.set_level(logging.DEBUG) - logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) - logger.debug("test debug") - logger.info("test info") - logger.warning("test warning") - logger.error("test error") - logger.critical("test critical") - - assert caplog.record_tuples == [ - ("root", 10, "test debug"), - ("root", 20, "test info"), - ("root", 30, "test warning"), - ("root", 40, "test error"), - ("root", 50, "test critical"), - ] - - def test_indents(self, caplog: pytest.LogCaptureFixture): - caplog.set_level(logging.DEBUG) - logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) - logger.info("test 1") - logger.indent(2) - logger.error("test %d", 2) - logger.indent() - logger.debug("test 3") - logger.warning("test 4") - logger.indent() - logger.critical("test 5") - logger.critical("test 6") - - assert caplog.record_tuples == [ - ("root", 20, "test 1"), - ("root", 40, " test 2"), - ("root", 10, " test 3"), - ("root", 30, " test 4"), - ("root", 50, " test 5"), - ("root", 50, " test 6"), - ] - - def test_dedents(self, caplog: pytest.LogCaptureFixture): - caplog.set_level(logging.DEBUG) - logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) - logger.indent(3) - logger.info("test 1") - logger.dedent(2) - logger.error("test %d", 2) - logger.dedent() - logger.debug("test 3") - logger.warning("test 4") - - assert caplog.record_tuples == [ - ("root", 20, " test 1"), - ("root", 40, " test 2"), - ("root", 10, "test 3"), - ("root", 30, "test 4"), - ] - - def test_cant_dedent_below_zero(self, caplog: pytest.LogCaptureFixture): - caplog.set_level(logging.DEBUG) - logger = IndentedLoggerAdapter(logging.getLogger()) - logger.dedent(4) - logger.info("test 1") - - assert caplog.record_tuples == [("root", 20, "test 1")] +def test_does_not_indent_without_intent(caplog: pytest.LogCaptureFixture): + caplog.set_level(logging.DEBUG) + logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) + logger.debug("test debug") + logger.info("test info") + logger.warning("test warning") + logger.error("test error") + logger.critical("test critical") + + assert caplog.record_tuples == [ + ("root", 10, "test debug"), + ("root", 20, "test info"), + ("root", 30, "test warning"), + ("root", 40, "test error"), + ("root", 50, "test critical"), + ] + + +def test_indents(caplog: pytest.LogCaptureFixture): + caplog.set_level(logging.DEBUG) + logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) + logger.info("test 1") + logger.indent(2) + logger.error("test %d", 2) + logger.indent() + logger.debug("test 3") + logger.warning("test 4") + logger.indent() + logger.critical("test 5") + logger.critical("test 6") + + assert caplog.record_tuples == [ + ("root", 20, "test 1"), + ("root", 40, " test 2"), + ("root", 10, " test 3"), + ("root", 30, " test 4"), + ("root", 50, " test 5"), + ("root", 50, " test 6"), + ] + + +def test_dedents(caplog: pytest.LogCaptureFixture): + caplog.set_level(logging.DEBUG) + logger = IndentedLoggerAdapter(logging.getLogger(), reset=True) + logger.indent(3) + logger.info("test 1") + logger.dedent(2) + logger.error("test %d", 2) + logger.dedent() + logger.debug("test 3") + logger.warning("test 4") + + assert caplog.record_tuples == [ + ("root", 20, " test 1"), + ("root", 40, " test 2"), + ("root", 10, "test 3"), + ("root", 30, "test 4"), + ] + + +def test_cant_dedent_below_zero(caplog: pytest.LogCaptureFixture): + caplog.set_level(logging.DEBUG) + logger = IndentedLoggerAdapter(logging.getLogger()) + logger.dedent(4) + logger.info("test 1") + + assert caplog.record_tuples == [("root", 20, "test 1")] + + +def test_current_indent_shared_by_multiple_loggers(caplog: pytest.LogCaptureFixture): + """Indenting one logger indents all loggers.""" + caplog.set_level(logging.DEBUG) + logger1 = IndentedLoggerAdapter(logging.getLogger("logger1"), reset=True) + logger1.info("test 1") + logger1.indent() + logger2 = IndentedLoggerAdapter(logging.getLogger("logger2")) + logger2.info("test 2") + logger3 = IndentedLoggerAdapter(logging.getLogger("logger3")) + logger3.indent() + logger3.info("test 3") + logger3.info("test 4") + logger2.dedent() + logger2.info("test 5") + + assert caplog.record_tuples == [ + ("logger1", 20, "test 1"), + ("logger2", 20, " test 2"), + ("logger3", 20, " test 3"), + ("logger3", 20, " test 4"), + ("logger2", 20, " test 5"), + ] diff --git a/tests/test_version_part.py b/tests/test_version_part.py index 085101ed..4875fc05 100644 --- a/tests/test_version_part.py +++ b/tests/test_version_part.py @@ -299,7 +299,7 @@ def test_parse_doesnt_parse_current_version(tmp_path: Path, caplog: LogCaptureFi with inside_dir(tmp_path): get_config_data(overrides) - assert "Evaluating 'parse' option: 'xxx' does not parse current version '12'" in caplog.messages + assert " Evaluating 'parse' option: 'xxx' does not parse current version '12'" in caplog.messages def test_part_does_not_revert_to_zero_if_optional(tmp_path: Path) -> None: