Skip to content

Commit

Permalink
Merge pull request #226 from callowayproject/225-show-bump-attributee…
Browse files Browse the repository at this point in the history
…rror

Fix version visualization and add verbose logging
  • Loading branch information
coordt authored Aug 13, 2024
2 parents 5a64ae0 + 5f25300 commit e5ee982
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 101 deletions.
11 changes: 10 additions & 1 deletion bumpversion/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,17 @@ def sample_config(prompt: bool, destination: str) -> None:
help="Config file to read most of the variables from.",
)
@click.option("--ascii", is_flag=True, help="Use ASCII characters only.")
def show_bump(version: str, config_file: Optional[str], ascii: bool) -> None:
@click.option(
"-v",
"--verbose",
count=True,
required=False,
envvar="BUMPVERSION_VERBOSE",
help="Print verbose logging to stderr. Can specify several times for more verbosity.",
)
def show_bump(version: str, config_file: Optional[str], ascii: bool, verbose: int) -> None:
"""Show the possible versions resulting from the bump subcommand."""
setup_logging(verbose)
found_config_file = find_config_file(config_file)
config = get_configuration(found_config_file)
if not version:
Expand Down
4 changes: 2 additions & 2 deletions bumpversion/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

if TYPE_CHECKING:
from bumpversion.scm import SCMInfo
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.models import VersionSpec
from bumpversion.versioning.version_config import VersionConfig

logger = get_indented_logger(__name__)

Expand Down Expand Up @@ -166,7 +166,7 @@ def files_to_modify(self) -> List[FileChange]:
@property
def version_config(self) -> "VersionConfig":
"""Return the version configuration."""
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.version_config import VersionConfig

return VersionConfig(self.parse, self.serialize, self.search, self.replace, self.parts)

Expand Down
2 changes: 1 addition & 1 deletion bumpversion/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from bumpversion.exceptions import VersionNotFoundError
from bumpversion.ui import get_indented_logger
from bumpversion.utils import get_nested_value, set_nested_value
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.models import Version, VersionComponentSpec
from bumpversion.versioning.version_config import VersionConfig

logger = get_indented_logger(__name__)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from click import UsageError

from bumpversion.exceptions import BumpVersionError
from bumpversion.ui import get_indented_logger
from bumpversion.utils import labels_for_format
from bumpversion.versioning.models import Version, VersionComponentSpec, VersionSpec
Expand All @@ -29,7 +30,7 @@ def __init__(
try:
self.parse_regex = re.compile(parse, re.VERBOSE)
except re.error as e:
raise UsageError(f"--parse '{parse}' is not a valid regex.") from e
raise UsageError(f"'{parse}' is not a valid regex.") from e

self.serialize_formats = serialize
self.part_configs = part_configs or {}
Expand All @@ -38,7 +39,7 @@ def __init__(
self.search = search
self.replace = replace

def __repr__(self) -> str:
def __repr__(self) -> str: # pragma: no-coverage
return f"<bumpversion.VersionConfig:{self.parse_regex.pattern}:{self.serialize_formats}>"

def __eq__(self, other: Any) -> bool:
Expand All @@ -63,19 +64,25 @@ def order(self) -> List[str]:
"""
return labels_for_format(self.serialize_formats[0])

def parse(self, version_string: Optional[str] = None) -> Optional[Version]:
def parse(self, version_string: Optional[str] = None, raise_error: bool = False) -> Optional[Version]:
"""
Parse a version string into a Version object.
Args:
version_string: Version string to parse
raise_error: Raise an exception if a version string is invalid
Returns:
A Version object representing the string.
Raises:
BumpversionException: If a version string is invalid and raise_error is True.
"""
parsed = parse_version(version_string, self.parse_regex.pattern)

if not parsed:
if not parsed and raise_error:
raise BumpVersionError(f"Unable to parse version {version_string} using {self.parse_regex.pattern}")
elif not parsed:
return None

version = self.version_spec.create_version(parsed)
Expand Down
7 changes: 4 additions & 3 deletions bumpversion/visualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def filter_version_parts(config: Config) -> List[str]:

def visualize(config: Config, version_str: str, box_style: str = "light") -> None:
"""Output a visualization of the bump-my-version bump process."""
version = config.version_config.parse(version_str)
version = config.version_config.parse(version_str, raise_error=True)
version_parts = filter_version_parts(config)
num_parts = len(version_parts)

Expand All @@ -120,7 +120,7 @@ def visualize(config: Config, version_str: str, box_style: str = "light") -> Non
version_lead = lead_string(version_str, border)
blank_lead = lead_string(version_str, border, blank=True)
version_part_length = max(len(part) for part in version_parts)

lines = []
for i, part in enumerate(version_parts):
line = [version_lead] if i == 0 else [blank_lead]

Expand All @@ -135,4 +135,5 @@ def visualize(config: Config, version_str: str, box_style: str = "light") -> Non
line.append(connection_str(border, has_next=has_next, has_previous=has_previous))
line.append(labeled_line(part, border, version_part_length))
line.append(next_version_str)
print_info("".join(line))
lines.append("".join(line))
print_info("\n".join(lines))
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def inside_dir(dirpath: Path) -> Generator:
def get_config_data(overrides: dict) -> tuple:
"""Get the configuration, version_config and version."""
from bumpversion import config
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.version_config import VersionConfig

conf = config.get_configuration(config_file="missing", **overrides)
version_config = VersionConfig(conf.parse, conf.serialize, conf.search, conf.replace, conf.parts)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ def test_commit_and_tag_with_config_file(mock_context):
def test_key_path_required_for_toml_change(tmp_path: Path, caplog):
"""If the key_path is not provided, the toml file should use standard search and replace."""
from bumpversion import config
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.version_config import VersionConfig

# Arrange
config_path = tmp_path / "pyproject.toml"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_configuredfile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for the ConfiguredFile class."""

from bumpversion.files import ConfiguredFile, FileChange
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.version_config import VersionConfig
from bumpversion.versioning.models import VersionComponentSpec


Expand Down
2 changes: 1 addition & 1 deletion tests/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from bumpversion.context import get_context
from bumpversion.exceptions import VersionNotFoundError
from bumpversion.ui import setup_logging, get_indented_logger
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.version_config import VersionConfig
from tests.conftest import get_config_data, inside_dir


Expand Down
86 changes: 0 additions & 86 deletions tests/test_version_part.py

This file was deleted.

29 changes: 29 additions & 0 deletions tests/test_versioning/test_models_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from freezegun import freeze_time

from bumpversion import exceptions
from bumpversion.versioning.models import VersionComponentSpec
from bumpversion.versioning.models import VersionSpec
import pytest
Expand Down Expand Up @@ -132,6 +133,34 @@ def test_changes_component_and_dependents(self, semver_version_spec: VersionSpec
assert major_version_str == "2.0.0.4.6"
assert build_version_str == "1.2.3.5.6"

def test_invalid_component_raises_error(self, semver_version_spec: VersionSpec):
"""If you bump a version with an invalid component name raises an error."""
version1 = semver_version_spec.create_version(
{"major": "1", "minor": "2", "patch": "3", "build": "4", "auto": "5"}
)
with pytest.raises(exceptions.InvalidVersionPartError, match="No part named 'bugfix'"):
version1.bump("bugfix")

def test_independent_component_is_independent(self, semver_version_spec: VersionSpec):
"""An independent component can only get bumped independently."""
# Arrange
version1 = semver_version_spec.create_version(
{"major": "1", "minor": "2", "patch": "3", "build": "4", "auto": "5"}
)

# Act & Assert
build_version_bump = version1.bump("build")
assert build_version_bump["build"].value == "5"
assert build_version_bump["major"].value == "1"

major_version_bump = build_version_bump.bump("major")
assert major_version_bump["build"].value == "5"
assert major_version_bump["major"].value == "2"

build_version_bump2 = major_version_bump.bump("build")
assert build_version_bump2["build"].value == "6"
assert build_version_bump2["major"].value == "2"

class TestRequiredComponents:
"""Tests of the required_keys function."""

Expand Down
79 changes: 79 additions & 0 deletions tests/test_versioning/test_version_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from pathlib import Path

import pytest
from click import UsageError

from bumpversion import exceptions
from bumpversion.context import get_context
from bumpversion.versioning.version_config import VersionConfig
from tests.conftest import get_config_data


def test_invalid_parse_raises_error():
"""An invalid parse regex value raises an error."""
with pytest.raises(exceptions.UsageError):
VersionConfig(r"(.+", ("{major}.{minor}.{patch}",), search="", replace="")


class TestParse:
"""Tests for parsing a version."""

def test_cant_parse_returns_none(self):
"""The default behavior when unable to parse a string is to return None."""
# Assemble
overrides = {
"current_version": "19.6.0",
"parse": r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).*",
"serialize": ["{major}.{minor}.{patch}"],
}
_, version_config, current_version = get_config_data(overrides)

# Act and Assert
assert version_config.parse("A.B.C") is None

def test_cant_parse_raises_error_when_set(self):
"""When `raise_error` is enabled, the inability to parse a string will raise an error."""
# Assemble
overrides = {
"current_version": "19.6.0",
"parse": r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).*",
"serialize": ["{major}.{minor}.{patch}"],
}
_, version_config, current_version = get_config_data(overrides)

# Act and Assert
with pytest.raises(exceptions.UsageError):
version_config.parse("A.B.C", raise_error=True)


class TestSerialize:
"""Tests for the VersionConfig.serialize() method."""

def test_distance_to_latest_tag_in_pattern(self):
"""Using ``distance_to_latest_tag`` in the serialization string outputs correctly."""
from bumpversion.scm import Git, SCMInfo

overrides = {
"current_version": "19.6.0",
"parse": r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).*",
"serialize": ["{major}.{minor}.{patch}-pre{distance_to_latest_tag}"],
}
conf, version_config, current_version = get_config_data(overrides)
conf.scm_info = SCMInfo(
tool=Git, commit_sha="1234123412341234", distance_to_latest_tag=3, current_version="19.6.0", dirty=False
)
assert version_config.serialize(current_version, get_context(conf)) == "19.6.0-pre3"

def test_environment_var_in_serialize_pattern(self):
"""Environment variables are serialized correctly."""
import os

os.environ["BUILD_NUMBER"] = "567"
overrides = {
"current_version": "2.3.4",
"parse": r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).*",
"serialize": ["{major}.{minor}.{patch}-pre{$BUILD_NUMBER}"],
}
conf, version_config, current_version = get_config_data(overrides)
assert version_config.serialize(current_version, get_context(conf)) == "2.3.4-pre567"
del os.environ["BUILD_NUMBER"]

0 comments on commit e5ee982

Please sign in to comment.