Skip to content

Commit

Permalink
Added always_increment attribute for parts
Browse files Browse the repository at this point in the history
This is a requirement for CalVer to ensure they always increment with each bump, but it will work for any type.
  • Loading branch information
coordt committed Mar 27, 2024
1 parent 5a3e05d commit 53ee848
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 30 deletions.
5 changes: 5 additions & 0 deletions bumpversion/versioning/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PartFunction:
first_value: str
optional_value: str
independent: bool
always_increment: bool

def bump(self, value: str) -> str:
"""Increase the value."""
Expand All @@ -32,6 +33,7 @@ def __init__(self, value: Union[str, int, None] = None):
self.first_value = str(value)
self.optional_value = str(value)
self.independent = True
self.always_increment = False

def bump(self, value: Optional[str] = None) -> str:
"""Return the optional value."""
Expand All @@ -46,6 +48,8 @@ def __init__(self, calver_format: str):
self.calver_format = calver_format
self.first_value = self.bump()
self.optional_value = "There isn't an optional value for CalVer."
self.independent = False
self.always_increment = True

def bump(self, value: Optional[str] = None) -> str:
"""Return the optional value."""
Expand Down Expand Up @@ -75,6 +79,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
self.always_increment = False

def bump(self, value: Union[str, int]) -> str:
"""Increase the first numerical value by one."""
Expand Down
52 changes: 46 additions & 6 deletions bumpversion/versioning/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from __future__ import annotations

from collections import defaultdict, deque
from typing import Any, Dict, List, Optional, Union
from itertools import chain
from typing import Any, Dict, List, Optional, Tuple, Union

from pydantic import BaseModel
from pydantic import BaseModel, model_validator

from bumpversion.exceptions import InvalidVersionPartError
from bumpversion.utils import key_val_string
Expand All @@ -26,13 +27,15 @@ def __init__(
optional_value: Optional[str] = None,
first_value: Union[str, int, None] = None,
independent: bool = False,
always_increment: bool = False,
calver_format: Optional[str] = None,
source: Optional[str] = None,
value: Union[str, int, None] = None,
):
self._value = str(value) if value is not None else None
self.func: Optional[PartFunction] = None
self.independent = independent
self.always_increment = always_increment
self.independent = True if always_increment else independent
self.source = source
self.calver_format = calver_format
if values:
Expand All @@ -58,6 +61,7 @@ def copy(self) -> "VersionComponent":
optional_value=self.func.optional_value,
first_value=self.func.first_value,
independent=self.independent,
always_increment=self.always_increment,
calver_format=self.calver_format,
source=self.source,
value=self._value,
Expand Down Expand Up @@ -123,20 +127,32 @@ class VersionComponentSpec(BaseModel):
independent: bool = False
"""Is the component independent of the other components?"""

always_increment: bool = False
"""Should the component always increment, even if it is not necessary?"""

calver_format: Optional[str] = None
"""The format string for a CalVer component."""

# source: Optional[str] = None # Name of environment variable or context variable to use as the source for value
depends_on: Optional[str] = None
"""The name of the component this component depends on."""

@model_validator(mode="before")
@classmethod
def set_always_increment_with_calver(cls, data: Any) -> Any:
"""Set always_increment to True if calver_format is present."""
if isinstance(data, dict) and data.get("calver_format"):
data["always_increment"] = True
return data

def create_component(self, value: Union[str, int, None] = None) -> VersionComponent:
"""Generate a version component from the configuration."""
return VersionComponent(
values=self.values,
optional_value=self.optional_value,
first_value=self.first_value,
independent=self.independent,
always_increment=self.always_increment,
calver_format=self.calver_format,
# source=self.source,
value=value,
Expand All @@ -158,6 +174,7 @@ def __init__(self, components: Dict[str, VersionComponentSpec], order: Optional[
self.order = order
self.dependency_map = defaultdict(list)
previous_component = self.order[0]
self.always_increment = [name for name, config in self.component_configs.items() if config.always_increment]
for component in self.order[1:]:
if self.component_configs[component].independent:
continue
Expand Down Expand Up @@ -231,12 +248,35 @@ def bump(self, component_name: str) -> "Version":
if component_name not in self.components:
raise InvalidVersionPartError(f"No part named {component_name!r}")

components_to_reset = self.version_spec.get_dependents(component_name)

new_values = dict(self.components.items())
new_values[component_name] = self.components[component_name].bump()
always_incr_values, components_to_reset = self._always_increment()
new_values.update(always_incr_values)

if component_name not in components_to_reset:
new_values[component_name] = self.components[component_name].bump()
components_to_reset |= set(self.version_spec.get_dependents(component_name))

for component in components_to_reset:
if not self.components[component].is_independent:
new_values[component] = self.components[component].null()

return Version(self.version_spec, new_values, self.original)

def _always_incr_dependencies(self) -> dict:
"""Return the components that always increment and depend on the given component."""
return {name: self.version_spec.get_dependents(name) for name in self.version_spec.always_increment}

def _increment_always_incr(self) -> dict:
"""Increase the values of the components that always increment."""
components = self.version_spec.always_increment
return {name: self.components[name].bump() for name in components}

def _always_increment(self) -> Tuple[dict, set]:
"""Return the components that always increment and their dependents."""
values = self._increment_always_incr()
dependents = self._always_incr_dependencies()
for component_name, value in values.items():
if value == self.components[component_name]:
dependents.pop(component_name, None)
unique_dependents = set(chain.from_iterable(dependents.values()))
return values, unique_dependents
12 changes: 8 additions & 4 deletions tests/fixtures/basic_cfg_expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,29 @@
'included_paths': [],
'message': 'Bump version: {current_version} → {new_version}',
'parse': '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?',
'parts': {'major': {'calver_format': None,
'parts': {'major': {'always_increment': False,
'calver_format': None,
'depends_on': None,
'first_value': None,
'independent': False,
'optional_value': None,
'values': None},
'minor': {'calver_format': None,
'minor': {'always_increment': False,
'calver_format': None,
'depends_on': None,
'first_value': None,
'independent': False,
'optional_value': None,
'values': None},
'patch': {'calver_format': None,
'patch': {'always_increment': False,
'calver_format': None,
'depends_on': None,
'first_value': None,
'independent': False,
'optional_value': None,
'values': None},
'release': {'calver_format': None,
'release': {'always_increment': False,
'calver_format': None,
'depends_on': None,
'first_value': None,
'independent': False,
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/basic_cfg_expected.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,31 @@ message: "Bump version: {current_version} → {new_version}"
parse: "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
parts:
major:
always_increment: false
calver_format: null
depends_on: null
first_value: null
independent: false
optional_value: null
values: null
minor:
always_increment: false
calver_format: null
depends_on: null
first_value: null
independent: false
optional_value: null
values: null
patch:
always_increment: false
calver_format: null
depends_on: null
first_value: null
independent: false
optional_value: null
values: null
release:
always_increment: false
calver_format: null
depends_on: null
first_value: null
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/basic_cfg_expected_full.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"parts": {
"major": {
"always_increment": false,
"calver_format": null,
"depends_on": null,
"first_value": null,
Expand All @@ -66,6 +67,7 @@
"values": null
},
"minor": {
"always_increment": false,
"calver_format": null,
"depends_on": null,
"first_value": null,
Expand All @@ -74,6 +76,7 @@
"values": null
},
"patch": {
"always_increment": false,
"calver_format": null,
"depends_on": null,
"first_value": null,
Expand All @@ -82,6 +85,7 @@
"values": null
},
"release": {
"always_increment": false,
"calver_format": null,
"depends_on": null,
"first_value": null,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_config/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest
from pytest import LogCaptureFixture, param, TempPathFactory

from bumpversion.utils import get_context
from bumpversion.context import get_context
from bumpversion import config
from bumpversion.config.files import find_config_file, CONFIG_FILE_SEARCH_ORDER
import bumpversion.config.utils
Expand Down Expand Up @@ -198,7 +198,7 @@ def test_pep440_config(git_repo: Path, fixtures_path: Path):
"""
Check the PEP440 config file.
"""
from bumpversion.utils import get_context
from bumpversion.context import get_context
from bumpversion.bump import get_next_version
from bumpversion import cli
import subprocess
Expand Down

0 comments on commit 53ee848

Please sign in to comment.