Skip to content

Commit

Permalink
Refactored versioning models and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
coordt committed Dec 23, 2023
1 parent 5ed546b commit 7d05414
Show file tree
Hide file tree
Showing 15 changed files with 623 additions and 182 deletions.
6 changes: 3 additions & 3 deletions bumpversion/bump.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ def get_next_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)
next_version = current_version.bump(version_part)
else:
raise ConfigurationError("Unable to get the next version.")

logger.info("Values are now: %s", key_val_string(next_version.values))
logger.info("Values are now: %s", key_val_string(next_version.components))
logger.dedent()
return next_version

Expand Down Expand Up @@ -113,7 +113,7 @@ def commit_and_tag(
dry_run: bool = False,
) -> None:
"""
Commit and tag the changes, if a tool is configured.
Commit and tag the changes if a tool is configured.
Args:
config: The configuration
Expand Down
19 changes: 8 additions & 11 deletions bumpversion/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,11 @@
if TYPE_CHECKING:
from bumpversion.scm import SCMInfo
from bumpversion.version_part import VersionConfig
from bumpversion.versioning.models import VersionComponentConfig, VersionSpec

logger = get_indented_logger(__name__)


class VersionPartConfig(BaseModel):
"""Configuration of a part of the version."""

values: Optional[list] = None # Optional. Numeric is used if missing or no items in list
optional_value: Optional[str] = None # Optional.
# Defaults to first value. 0 in the case of numeric. Empty string means nothing is optional.
first_value: Union[str, int, None] = None # Optional. Defaults to first value in values
independent: bool = False


class FileChange(BaseModel):
"""A change to make to a file."""

Expand Down Expand Up @@ -100,7 +91,7 @@ class Config(BaseSettings):
message: str
commit_args: Optional[str]
scm_info: Optional["SCMInfo"]
parts: Dict[str, VersionPartConfig]
parts: Dict[str, VersionComponentConfig]
files: List[FileChange] = Field(default_factory=list)
included_paths: List[str] = Field(default_factory=list)
excluded_paths: List[str] = Field(default_factory=list)
Expand Down Expand Up @@ -169,3 +160,9 @@ def version_config(self) -> "VersionConfig":
from bumpversion.version_part import VersionConfig

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

def version_spec(self, version: Optional[str] = None) -> "VersionSpec":
"""Return the version specification."""
from bumpversion.versioning.models import VersionSpec

return VersionSpec(self.parts)
31 changes: 21 additions & 10 deletions bumpversion/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
from __future__ import annotations

import glob
import itertools
from typing import Dict, List

from bumpversion.config.models import FileChange, VersionPartConfig
from bumpversion.utils import labels_for_format
from exceptions import BumpVersionError

from bumpversion.config.models import FileChange
from bumpversion.versioning.models import VersionComponentConfig


def get_all_file_configs(config_dict: dict) -> List[FileChange]:
Expand All @@ -25,15 +26,25 @@ def get_all_file_configs(config_dict: dict) -> List[FileChange]:
return [FileChange(**f) for f in files]


def get_all_part_configs(config_dict: dict) -> Dict[str, VersionPartConfig]:
def get_all_part_configs(config_dict: dict) -> Dict[str, VersionComponentConfig]:
"""Make sure all version parts are included."""
serialize = config_dict["serialize"]
import re

try:
parsing_groups = list(re.compile(config_dict["parse"]).groupindex.keys())
except re.error as e:
raise BumpVersionError(f"Could not parse regex '{config_dict['parse']}': {e}") from e
parts = config_dict["parts"]
all_labels = set(itertools.chain.from_iterable([labels_for_format(fmt) for fmt in serialize]))
return {
label: VersionPartConfig(**parts[label]) if label in parts else VersionPartConfig() # type: ignore[call-arg]
for label in all_labels
}

part_configs = {}
for label in parsing_groups:
is_independent = label.startswith("$")
part_configs[label] = (
VersionComponentConfig(**parts[label])
if label in parts
else VersionComponentConfig(independent=is_independent)
)
return part_configs


def resolve_glob_files(file_cfg: FileChange) -> List[FileChange]:
Expand Down
6 changes: 4 additions & 2 deletions bumpversion/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from pathlib import Path
from typing import Dict, List, MutableMapping, Optional

from bumpversion.config.models import FileChange, VersionPartConfig
from versioning.models import VersionComponentConfig

from bumpversion.config.models import FileChange
from bumpversion.exceptions import VersionNotFoundError
from bumpversion.ui import get_indented_logger
from bumpversion.version_part import VersionConfig
Expand Down Expand Up @@ -297,7 +299,7 @@ class DataFileUpdater:
def __init__(
self,
file_change: FileChange,
version_part_configs: Dict[str, VersionPartConfig],
version_part_configs: Dict[str, VersionComponentConfig],
) -> None:
self.file_change = file_change
self.version_config = VersionConfig(
Expand Down
25 changes: 15 additions & 10 deletions bumpversion/version_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from typing import Any, Dict, List, MutableMapping, Optional, Tuple

from click import UsageError
from versioning.models import VersionComponentConfig

from bumpversion.config.models import VersionPartConfig
from bumpversion.exceptions import FormattingError, MissingValueError
from bumpversion.ui import get_indented_logger
from bumpversion.utils import labels_for_format
from bumpversion.versioning.models import Version, VersionPart
from bumpversion.versioning.models import Version, VersionComponent, VersionSpec
from bumpversion.versioning.serialization import parse_version

logger = get_indented_logger(__name__)
Expand All @@ -27,7 +27,7 @@ def __init__(
serialize: Tuple[str],
search: str,
replace: str,
part_configs: Optional[Dict[str, VersionPartConfig]] = None,
part_configs: Optional[Dict[str, VersionComponentConfig]] = None,
):
try:
self.parse_regex = re.compile(parse, re.VERBOSE)
Expand All @@ -36,6 +36,7 @@ def __init__(

self.serialize_formats = serialize
self.part_configs = part_configs or {}
self.version_spec = VersionSpec(self.part_configs)
# TODO: I think these two should be removed from the config object
self.search = search
self.replace = replace
Expand Down Expand Up @@ -80,12 +81,16 @@ def parse(self, version_string: Optional[str] = None) -> Optional[Version]:
if not parsed:
return None

_parsed = {
key: VersionPart(self.part_configs[key], value)
for key, value in parsed.items()
if key in self.part_configs
}
return Version(_parsed, version_string)
version = self.version_spec.create_version(parsed)
version.original = version_string
return version

# _parsed = {
# key: VersionComponent(self.part_configs[key], value)
# for key, value in parsed.items()
# if key in self.part_configs
# }
# return Version(_parsed, version_string)

def _serialize(
self, version: Version, serialize_format: str, context: MutableMapping, raise_if_incomplete: bool = False
Expand Down Expand Up @@ -128,7 +133,7 @@ def _serialize(
for i, k in enumerate(keys):
v = values[k]

if not isinstance(v, VersionPart):
if not isinstance(v, VersionComponent):
# values coming from environment variables don't need
# representation
continue
Expand Down
19 changes: 19 additions & 0 deletions bumpversion/versioning/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ def bump(self, value: str) -> str:
raise NotImplementedError


class IndependentFunction(PartFunction):
"""
This is a class that provides an independent function for version parts.
It simply returns the optional value, which is equal to the first value.
"""

def __init__(self, value: Union[str, int, None] = None):
self.first_value = str(value)
self.optional_value = str(value)
self.independent = True

def bump(self, value: str) -> str:
"""Return the optional value."""
return self.optional_value


class NumericFunction(PartFunction):
"""
This is a class that provides a numeric function for version parts.
Expand All @@ -37,6 +54,7 @@ def __init__(self, optional_value: Union[str, int, None] = None, first_value: Un

self.first_value = str(first_value or 0)
self.optional_value = str(optional_value or self.first_value)
self.independent = False

def bump(self, value: Union[str, int]) -> str:
"""Increase the first numerical value by one."""
Expand Down Expand Up @@ -80,6 +98,7 @@ def __init__(
raise ValueError("Version part values cannot be empty")

self._values = values
self.independent = False

if optional_value is None:
optional_value = values[0]
Expand Down
Loading

0 comments on commit 7d05414

Please sign in to comment.