diff --git a/bumpversion/config/models.py b/bumpversion/config/models.py index 8bbdac3c..f7cfbce6 100644 --- a/bumpversion/config/models.py +++ b/bumpversion/config/models.py @@ -10,7 +10,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict from bumpversion.ui import get_indented_logger -from bumpversion.versioning.models import VersionComponentConfig # NOQA: TCH001 +from bumpversion.versioning.models import VersionComponentSpec # NOQA: TCH001 if TYPE_CHECKING: from bumpversion.scm import SCMInfo @@ -92,7 +92,7 @@ class Config(BaseSettings): message: str commit_args: Optional[str] scm_info: Optional["SCMInfo"] - parts: Dict[str, VersionComponentConfig] + parts: Dict[str, VersionComponentSpec] files: List[FileChange] = Field(default_factory=list) included_paths: List[str] = Field(default_factory=list) excluded_paths: List[str] = Field(default_factory=list) diff --git a/bumpversion/config/utils.py b/bumpversion/config/utils.py index 08667770..9c6d3df5 100644 --- a/bumpversion/config/utils.py +++ b/bumpversion/config/utils.py @@ -6,7 +6,7 @@ from bumpversion.config.models import FileChange from bumpversion.exceptions import BumpVersionError -from bumpversion.versioning.models import VersionComponentConfig +from bumpversion.versioning.models import VersionComponentSpec def get_all_file_configs(config_dict: dict) -> List[FileChange]: @@ -25,7 +25,7 @@ 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, VersionComponentConfig]: +def get_all_part_configs(config_dict: dict) -> Dict[str, VersionComponentSpec]: """Make sure all version parts are included.""" import re @@ -39,9 +39,9 @@ def get_all_part_configs(config_dict: dict) -> Dict[str, VersionComponentConfig] for label in parsing_groups: is_independent = label.startswith("$") part_configs[label] = ( - VersionComponentConfig(**parts[label]) + VersionComponentSpec(**parts[label]) if label in parts - else VersionComponentConfig(independent=is_independent) + else VersionComponentSpec(independent=is_independent) ) return part_configs diff --git a/bumpversion/files.py b/bumpversion/files.py index 831fd7cd..6a5eb015 100644 --- a/bumpversion/files.py +++ b/bumpversion/files.py @@ -11,7 +11,7 @@ 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, VersionComponentConfig +from bumpversion.versioning.models import Version, VersionComponentSpec logger = get_indented_logger(__name__) @@ -300,7 +300,7 @@ class DataFileUpdater: def __init__( self, file_change: FileChange, - version_part_configs: Dict[str, VersionComponentConfig], + version_part_configs: Dict[str, VersionComponentSpec], ) -> None: self.file_change = file_change self.version_config = VersionConfig( diff --git a/bumpversion/version_part.py b/bumpversion/version_part.py index 5f1352a6..57cde5db 100644 --- a/bumpversion/version_part.py +++ b/bumpversion/version_part.py @@ -6,7 +6,7 @@ from bumpversion.ui import get_indented_logger from bumpversion.utils import labels_for_format -from bumpversion.versioning.models import Version, VersionComponentConfig, VersionSpec +from bumpversion.versioning.models import Version, VersionComponentSpec, VersionSpec from bumpversion.versioning.serialization import parse_version, serialize logger = get_indented_logger(__name__) @@ -23,7 +23,7 @@ def __init__( serialize: Tuple[str], search: str, replace: str, - part_configs: Optional[Dict[str, VersionComponentConfig]] = None, + part_configs: Optional[Dict[str, VersionComponentSpec]] = None, ): try: self.parse_regex = re.compile(parse, re.VERBOSE) diff --git a/bumpversion/versioning/conventions.py b/bumpversion/versioning/conventions.py index e40617c1..9a956f9d 100644 --- a/bumpversion/versioning/conventions.py +++ b/bumpversion/versioning/conventions.py @@ -1,5 +1,5 @@ """Standard version conventions.""" -from bumpversion.versioning.models import VersionComponentConfig, VersionSpec +from bumpversion.versioning.models import VersionComponentSpec, VersionSpec # Adapted from https://packaging.python.org/en/latest/specifications/version-specifiers/ PEP440_PATTERN = r""" @@ -54,14 +54,14 @@ "{major}.{minor}.{patch}", ] PEP440_COMPONENT_CONFIGS = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), - "pre_l": VersionComponentConfig(values=["", "a", "b", "rc"]), - "pre_n": VersionComponentConfig(), - "post": VersionComponentConfig(depends_on="patch"), - "dev": VersionComponentConfig(depends_on="patch"), - "local": VersionComponentConfig(depends_on="patch", optional_value=""), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), + "pre_l": VersionComponentSpec(values=["", "a", "b", "rc"]), + "pre_n": VersionComponentSpec(), + "post": VersionComponentSpec(depends_on="patch"), + "dev": VersionComponentSpec(depends_on="patch"), + "local": VersionComponentSpec(depends_on="patch", optional_value=""), } @@ -95,12 +95,12 @@ def pep440_version_spec() -> VersionSpec: "{major}.{minor}.{patch}", ] SEMVER_COMPONENT_CONFIGS = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), - "pre_l": VersionComponentConfig(values=["", "a", "b", "rc"]), - "pre_n": VersionComponentConfig(), - "buildmetadata": VersionComponentConfig(independent=True), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), + "pre_l": VersionComponentSpec(values=["", "a", "b", "rc"]), + "pre_n": VersionComponentSpec(), + "buildmetadata": VersionComponentSpec(independent=True), } diff --git a/bumpversion/versioning/models.py b/bumpversion/versioning/models.py index 9953c934..181140d3 100644 --- a/bumpversion/versioning/models.py +++ b/bumpversion/versioning/models.py @@ -93,7 +93,7 @@ def __eq__(self, other: Any) -> bool: return self.value == other.value if isinstance(other, VersionComponent) else False -class VersionComponentConfig(BaseModel): +class VersionComponentSpec(BaseModel): """ Configuration of a version component. @@ -123,7 +123,7 @@ def create_component(self, value: Union[str, int, None] = None) -> VersionCompon class VersionSpec: """The specification of a version's components and their relationships.""" - def __init__(self, components: Dict[str, VersionComponentConfig], order: Optional[List[str]] = None): + def __init__(self, components: Dict[str, VersionComponentSpec], order: Optional[List[str]] = None): if not components: raise ValueError("A VersionSpec must have at least one component.") if not order: diff --git a/tests/test_configuredfile.py b/tests/test_configuredfile.py index 8eae6ead..7acb3fce 100644 --- a/tests/test_configuredfile.py +++ b/tests/test_configuredfile.py @@ -1,7 +1,7 @@ """Tests for the ConfiguredFile class.""" from bumpversion.files import ConfiguredFile, FileChange from bumpversion.version_part import VersionConfig -from bumpversion.versioning.models import VersionComponentConfig +from bumpversion.versioning.models import VersionComponentSpec class TestConfiguredFile: @@ -27,9 +27,9 @@ def test_file_change_is_identical_to_input(self): search="{current_version}", replace="{new_version}", part_configs={ - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), }, ) cfg_file = ConfiguredFile(change, version_config) @@ -53,9 +53,9 @@ def test_version_config_uses_file_change_attrs(self): search="{current_version}", replace="{new_version}", part_configs={ - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), }, ) expected = VersionConfig( diff --git a/tests/test_versioning/test_models_version.py b/tests/test_versioning/test_models_version.py index 19e73e1b..381729d1 100644 --- a/tests/test_versioning/test_models_version.py +++ b/tests/test_versioning/test_models_version.py @@ -1,6 +1,6 @@ """Tests for the Version class.""" -from bumpversion.versioning.models import VersionComponentConfig +from bumpversion.versioning.models import VersionComponentSpec from bumpversion.versioning.models import VersionSpec import pytest from pytest import param @@ -10,10 +10,10 @@ def version_spec(): """Return a version spec.""" config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), - "build": VersionComponentConfig(optional_value="0", independent=True), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), + "build": VersionComponentSpec(optional_value="0", independent=True), } return VersionSpec(config) diff --git a/tests/test_versioning/test_models_versioncomponent.py b/tests/test_versioning/test_models_versioncomponent.py index e0f82605..b100d521 100644 --- a/tests/test_versioning/test_models_versioncomponent.py +++ b/tests/test_versioning/test_models_versioncomponent.py @@ -1,6 +1,6 @@ """Tests of the VersionPart model.""" -from bumpversion.versioning.models import VersionComponentConfig +from bumpversion.versioning.models import VersionComponentSpec from bumpversion.versioning.functions import ValuesFunction, NumericFunction import pytest @@ -15,7 +15,7 @@ ) def version_component_config(request): """Return a three-part and a two-part version part configuration.""" - return VersionComponentConfig(**request.param) + return VersionComponentSpec(**request.param) class TestVersionComponent: @@ -27,11 +27,11 @@ def test_none_value_uses_optional_value(self, version_component_config): def test_config_with_values_selects_values_function(self): values = ["0", "1", "2"] - vp = VersionComponentConfig(values=values).create_component() + vp = VersionComponentSpec(values=values).create_component() assert isinstance(vp.func, ValuesFunction) def test_config_without_values_selects_numeric_function(self): - vp = VersionComponentConfig().create_component() + vp = VersionComponentSpec().create_component() assert isinstance(vp.func, NumericFunction) def test_copy_returns_new_version_part(self, version_component_config): diff --git a/tests/test_versioning/test_models_versionspec.py b/tests/test_versioning/test_models_versionspec.py index 62bf8c8a..edcef5de 100644 --- a/tests/test_versioning/test_models_versionspec.py +++ b/tests/test_versioning/test_models_versionspec.py @@ -2,7 +2,7 @@ import pytest from bumpversion.versioning.models import VersionSpec -from bumpversion.versioning.models import VersionComponentConfig +from bumpversion.versioning.models import VersionComponentSpec class TestVersionSpec: @@ -21,9 +21,9 @@ def test_empty_order_uses_order_of_components(self): """If the order is empty, it uses the component order.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } # Act @@ -36,9 +36,9 @@ def test_extra_items_raises_error(self): """If the order contains component names that do not exist, it raises and error.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } # Act @@ -49,9 +49,9 @@ def test_subset_of_items_works_fine(self): """An order containing a subset of component names works fine.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } # Act @@ -64,9 +64,9 @@ def test_dependency_map_follows_order(self): """The order of components correctly creates the dependency map.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } # Act @@ -81,9 +81,9 @@ def test_dependency_map_skips_independent_components(self): """Independent components are not in the dependency map.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(independent=True), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(independent=True), + "patch": VersionComponentSpec(), } # Act @@ -99,10 +99,10 @@ def test_empty_values_creates_default_version(self): """An empty values dict raises an error.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), - "build": VersionComponentConfig(independent=True), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), + "build": VersionComponentSpec(independent=True), } version_spec = VersionSpec(config) @@ -119,10 +119,10 @@ def test_missing_values_uses_component_first_value(self): """A missing value raises an error.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), - "build": VersionComponentConfig(independent=True), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), + "build": VersionComponentSpec(independent=True), } version_spec = VersionSpec(config) @@ -139,9 +139,9 @@ def test_extra_values_ignored(self): """Extra values are ignored.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } version_spec = VersionSpec(config) @@ -161,9 +161,9 @@ def test_bad_component_name_returns_empty_list(self): """Getting the dependents of a non-existing component returns an empty list.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } version_spec = VersionSpec(config) @@ -177,9 +177,9 @@ def test_extra_values_ignored(self): """Extra values are ignored.""" # Arrange config = { - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), } version_spec = VersionSpec(config) diff --git a/tests/test_versioning/test_serialization.py b/tests/test_versioning/test_serialization.py index 4d9c6ff5..3e078c95 100644 --- a/tests/test_versioning/test_serialization.py +++ b/tests/test_versioning/test_serialization.py @@ -1,7 +1,7 @@ """Tests for the serialization of versioned objects.""" from bumpversion.versioning.serialization import parse_version, serialize from bumpversion.versioning.conventions import semver_spec, SEMVER_PATTERN -from bumpversion.versioning.models import Version, VersionSpec, VersionComponentConfig +from bumpversion.versioning.models import Version, VersionSpec, VersionComponentSpec from bumpversion.exceptions import BumpVersionError, FormattingError import pytest @@ -130,10 +130,10 @@ def test_includes_optional_component_when_dependent_is_not_optional(self): """A format with an optional component should render when the dependent component is not optional.""" version_spec = VersionSpec( components={ - "major": VersionComponentConfig(), - "minor": VersionComponentConfig(), - "patch": VersionComponentConfig(), - "release": VersionComponentConfig( + "major": VersionComponentSpec(), + "minor": VersionComponentSpec(), + "patch": VersionComponentSpec(), + "release": VersionComponentSpec( optional_value="g", first_value="g", values=[ @@ -143,7 +143,7 @@ def test_includes_optional_component_when_dependent_is_not_optional(self): "g", ], ), - "build": VersionComponentConfig(), + "build": VersionComponentSpec(), }, ) serialize_patterns = [ @@ -162,6 +162,18 @@ def test_includes_optional_component_when_dependent_is_not_optional(self): == "0.3.1g1" ) + def test_selects_first_pattern_when_all_are_invalid(self): + """If all patterns are invalid, the first pattern should be selected.""" + version = semver_spec().create_version({"major": "1", "minor": "2", "patch": "3"}) + assert ( + serialize( + version, + serialize_patterns=["{major}", "{major}.{minor}"], + context={}, + ) + == "1" + ) + class TestRendersFormat: def test_with_newlines(self): """A serialization format with newlines should be rendered correctly."""