Skip to content

Commit

Permalink
Changed the management of file changes
Browse files Browse the repository at this point in the history
File changes are hashable to weed out duplication.
  • Loading branch information
coordt committed Dec 15, 2023
1 parent 84556f8 commit 909396d
Showing 7 changed files with 53 additions and 38 deletions.
15 changes: 11 additions & 4 deletions bumpversion/config/models.py
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ class FileChange(BaseModel):
"""A change to make to a file."""

parse: str
serialize: List[str]
serialize: tuple
search: str
replace: str
regex: bool
@@ -41,6 +41,10 @@ class FileChange(BaseModel):
glob: Optional[str] = None # Conflicts with filename. If both are specified, glob wins
key_path: Optional[str] = None # If specified, and has an appropriate extension, will be treated as a data file

def __hash__(self):
"""Return a hash of the model."""
return hash(tuple(sorted(self.model_dump().items())))

def get_search_pattern(self, context: MutableMapping) -> Tuple[re.Pattern, str]:
"""
Render the search pattern and return the compiled regex pattern and the raw pattern.
@@ -82,7 +86,7 @@ class Config(BaseSettings):

current_version: Optional[str]
parse: str
serialize: List[str] = Field(min_length=1)
serialize: tuple = Field(min_length=1)
search: str
replace: str
regex: bool
@@ -97,7 +101,7 @@ class Config(BaseSettings):
commit_args: Optional[str]
scm_info: Optional["SCMInfo"]
parts: Dict[str, VersionPartConfig]
files: List[FileChange]
files: List[FileChange] = Field(default_factory=list)
included_paths: List[str] = Field(default_factory=list)
excluded_paths: List[str] = Field(default_factory=list)
model_config = SettingsConfigDict(env_prefix="bumpversion_")
@@ -106,8 +110,9 @@ class Config(BaseSettings):
def add_files(self, filename: Union[str, List[str]]) -> None:
"""Add a filename to the list of files."""
filenames = [filename] if isinstance(filename, str) else filename
files = set(self.files)
for name in filenames:
self.files.append(
files.add(
FileChange(
filename=name,
glob=None,
@@ -120,6 +125,8 @@ def add_files(self, filename: Union[str, List[str]]) -> None:
ignore_missing_version=self.ignore_missing_version,
)
)
self.files = list(files)

self._resolved_filemap = None

@property
4 changes: 2 additions & 2 deletions bumpversion/version_part.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
import re
import string
from copy import copy
from typing import Any, Dict, List, MutableMapping, Optional, Union
from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union

from click import UsageError

@@ -134,7 +134,7 @@ class VersionConfig:
def __init__(
self,
parse: str,
serialize: List[str],
serialize: Tuple[str],
search: str,
replace: str,
part_configs: Optional[Dict[str, VersionPartConfig]] = None,
11 changes: 6 additions & 5 deletions bumpversion/yaml_dump.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
from collections import UserDict
from io import StringIO
from textwrap import indent
from typing import Any, Callable
from typing import Any, Callable, Union

DumperFunc = Callable[[Any], str]

@@ -89,7 +89,7 @@ def format_dict(val: dict) -> str:

for key, value in sorted(val.items()):
rendered_value = dump(value).strip()
if isinstance(value, (dict, list)):
if isinstance(value, (dict, list, tuple)):
rendered_value = f"\n{indent(rendered_value, INDENT)}"
else:
rendered_value = f" {rendered_value}"
@@ -101,7 +101,7 @@ def format_dict(val: dict) -> str:
YAML_DUMPERS.add_dumper(dict, format_dict)


def format_list(val: list) -> str:
def format_sequence(val: Union[list, tuple]) -> str:
"""Return a string representation of a value."""
buffer = StringIO()

@@ -110,7 +110,7 @@ def format_list(val: list) -> str:
if isinstance(item, dict):
rendered_value = indent(rendered_value, INDENT).strip()

if isinstance(item, list):
if isinstance(item, (list, tuple)):
rendered_value = f"\n{indent(rendered_value, INDENT)}"
else:
rendered_value = f" {rendered_value}"
@@ -119,7 +119,8 @@ def format_list(val: list) -> str:
return buffer.getvalue()


YAML_DUMPERS.add_dumper(list, format_list)
YAML_DUMPERS.add_dumper(list, format_sequence)
YAML_DUMPERS.add_dumper(tuple, format_sequence)


def format_none(_: None) -> str:
14 changes: 7 additions & 7 deletions tests/fixtures/basic_cfg_expected.txt
Original file line number Diff line number Diff line change
@@ -11,8 +11,8 @@
'regex': False,
'replace': '{new_version}',
'search': '{current_version}',
'serialize': ['{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}']},
'serialize': ('{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}')},
{'filename': 'bumpversion/__init__.py',
'glob': None,
'ignore_missing_version': False,
@@ -21,8 +21,8 @@
'regex': False,
'replace': '{new_version}',
'search': '{current_version}',
'serialize': ['{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}']},
'serialize': ('{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}')},
{'filename': 'CHANGELOG.md',
'glob': None,
'ignore_missing_version': False,
@@ -31,8 +31,8 @@
'regex': False,
'replace': '**unreleased**\n**v{new_version}**',
'search': '**unreleased**',
'serialize': ['{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}']}],
'serialize': ('{major}.{minor}.{patch}-{release}',
'{major}.{minor}.{patch}')}],
'ignore_missing_version': False,
'included_paths': [],
'message': 'Bump version: {current_version} → {new_version}',
@@ -63,7 +63,7 @@
'short_branch_name': None,
'tool': None},
'search': '{current_version}',
'serialize': ['{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}'],
'serialize': ('{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}'),
'sign_tags': False,
'tag': True,
'tag_message': 'Bump version: {current_version} → {new_version}',
30 changes: 15 additions & 15 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -139,7 +139,7 @@ def test_cli_options_override_config(tmp_path: Path, fixtures_path: Path, mocker
assert the_config.current_version == "1.1.0"
assert the_config.allow_dirty
assert the_config.parse == r"XXX(?P<spam>\d+);(?P<blob>\d+);(?P<slurp>\d+)"
assert the_config.serialize == ["XXX{spam};{blob};{slurp}"]
assert the_config.serialize == ("XXX{spam};{blob};{slurp}",)
assert the_config.search == "my-search"
assert the_config.replace == "my-replace"
assert the_config.commit is False
@@ -203,7 +203,7 @@ def test_listing_with_version_part(tmp_path: Path, fixtures_path: Path):
"current_version=1.0.0",
"excluded_paths=[]",
"parse=(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"serialize=['{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}']",
"serialize=('{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}')",
"search={current_version}",
"replace={new_version}",
"regex=False",
@@ -220,19 +220,19 @@ def test_listing_with_version_part(tmp_path: Path, fixtures_path: Path):
(
"files=[{'parse': "
"'(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(\\\\-(?P<release>[a-z]+))?', "
"'serialize': ['{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'], 'search': '{current_version}', 'replace': "
"'serialize': ('{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'), 'search': '{current_version}', 'replace': "
"'{new_version}', 'regex': False, 'ignore_missing_version': False, "
"'filename': 'setup.py', 'glob': None, 'key_path': None}, {'parse': "
"'(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(\\\\-(?P<release>[a-z]+))?', "
"'serialize': ['{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'], 'search': '{current_version}', 'replace': "
"'serialize': ('{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'), 'search': '{current_version}', 'replace': "
"'{new_version}', 'regex': False, 'ignore_missing_version': False, "
"'filename': 'bumpversion/__init__.py', 'glob': None, 'key_path': None}, "
"{'parse': "
"'(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(\\\\-(?P<release>[a-z]+))?', "
"'serialize': ['{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'], 'search': '**unreleased**', 'replace': "
"'serialize': ('{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'), 'search': '**unreleased**', 'replace': "
"'**unreleased**\\n**v{new_version}**', 'regex': False, "
"'ignore_missing_version': False, 'filename': 'CHANGELOG.md', 'glob': None, "
"'key_path': None}]"
@@ -263,7 +263,7 @@ def test_listing_without_version_part(tmp_path: Path, fixtures_path: Path):
"current_version=1.0.0",
"excluded_paths=[]",
"parse=(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
"serialize=['{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}']",
"serialize=('{major}.{minor}.{patch}-{release}', '{major}.{minor}.{patch}')",
"search={current_version}",
"replace={new_version}",
"regex=False",
@@ -280,19 +280,19 @@ def test_listing_without_version_part(tmp_path: Path, fixtures_path: Path):
(
"files=[{'parse': "
"'(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(\\\\-(?P<release>[a-z]+))?', "
"'serialize': ['{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'], 'search': '{current_version}', 'replace': "
"'serialize': ('{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'), 'search': '{current_version}', 'replace': "
"'{new_version}', 'regex': False, 'ignore_missing_version': False, "
"'filename': 'setup.py', 'glob': None, 'key_path': None}, {'parse': "
"'(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(\\\\-(?P<release>[a-z]+))?', "
"'serialize': ['{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'], 'search': '{current_version}', 'replace': "
"'serialize': ('{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'), 'search': '{current_version}', 'replace': "
"'{new_version}', 'regex': False, 'ignore_missing_version': False, "
"'filename': 'bumpversion/__init__.py', 'glob': None, 'key_path': None}, "
"{'parse': "
"'(?P<major>\\\\d+)\\\\.(?P<minor>\\\\d+)\\\\.(?P<patch>\\\\d+)(\\\\-(?P<release>[a-z]+))?', "
"'serialize': ['{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'], 'search': '**unreleased**', 'replace': "
"'serialize': ('{major}.{minor}.{patch}-{release}', "
"'{major}.{minor}.{patch}'), 'search': '**unreleased**', 'replace': "
"'**unreleased**\\n**v{new_version}**', 'regex': False, "
"'ignore_missing_version': False, 'filename': 'CHANGELOG.md', 'glob': None, "
"'key_path': None}]"
4 changes: 2 additions & 2 deletions tests/test_config/test_files.py
Original file line number Diff line number Diff line change
@@ -140,7 +140,7 @@ def test_multiple_config_files(tmp_path: Path):

assert cfg.current_version == "0.10.5"
assert cfg.parse == "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
assert cfg.serialize == ["{major}.{minor}.{patch}-{release}", "{major}.{minor}.{patch}"]
assert cfg.serialize == ("{major}.{minor}.{patch}-{release}", "{major}.{minor}.{patch}")


TOML_EXPECTED_DIFF = (
@@ -283,7 +283,7 @@ def test_file_overrides_config(fixtures_path: Path):
assert file_map["should_override_parse.txt"].ignore_missing_version == conf.ignore_missing_version

assert file_map["should_override_serialize.txt"].parse == conf.parse
assert file_map["should_override_serialize.txt"].serialize == ["{major}"]
assert file_map["should_override_serialize.txt"].serialize == ("{major}",)
assert file_map["should_override_serialize.txt"].search == conf.search
assert file_map["should_override_serialize.txt"].replace == conf.replace
assert file_map["should_override_serialize.txt"].regex == conf.regex
13 changes: 10 additions & 3 deletions tests/test_yaml.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@


def test_dump_unknown():
assert yaml_dump.dump((1, 2)) == '"(1, 2)"'
assert yaml_dump.dump({1, 2}) == '"{1, 2}"'


def test_format_str():
@@ -42,10 +42,15 @@ def test_format_dict():
"key8": True,
"key9": False,
"key10": 1.43,
"key11": (1, 2, 3),
}
expected = (
'key: "strval"\n'
"key10: 1.43\n"
"key11:\n"
" - 1\n"
" - 2\n"
" - 3\n"
"key2: 30\n"
"key3: 2023-06-19 13:45:30\n"
"key4: 2023-06-19\n"
@@ -63,8 +68,10 @@ def test_format_dict():


def test_format_list():
assert yaml_dump.format_list(["item"]) == '- "item"\n'
assert yaml_dump.format_list(["item", ["item2"]]) == '- "item"\n-\n - "item2"\n'
assert yaml_dump.format_sequence(["item"]) == '- "item"\n'
assert yaml_dump.format_sequence(["item", ["item2"]]) == '- "item"\n-\n - "item2"\n'
assert yaml_dump.format_sequence(("item",)) == '- "item"\n'
assert yaml_dump.format_sequence(("item", ("item2",))) == '- "item"\n-\n - "item2"\n'


def test_dump_none_val():

0 comments on commit 909396d

Please sign in to comment.