Skip to content

Commit 88e7f71

Browse files
committed
Refactored verioning models
- created a "conventions" module for future release - added an optional `depends_on` version component configuration - The `depends_on` is required for PEP440 versioning
1 parent f8c4d05 commit 88e7f71

10 files changed

+698
-65
lines changed

bumpversion/versioning/conventions.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""Standard version conventions."""
2+
from bumpversion.versioning.models import VersionComponentConfig, VersionSpec
3+
4+
# Adapted from https://packaging.python.org/en/latest/specifications/version-specifiers/
5+
PEP440_PATTERN = r"""
6+
v?
7+
(?:
8+
(?P<major>0|[1-9]\d*)\.
9+
(?P<minor>0|[1-9]\d*)\.
10+
(?P<patch>0|[1-9]\d*)
11+
(?: # pre-release
12+
[-_\.]?
13+
(?P<pre>
14+
(?P<pre_l>a|b|c|rc|alpha|beta|pre|preview)
15+
[-_\.]?
16+
(?P<pre_n>[0-9]+)?
17+
)
18+
)?
19+
(?: # post release
20+
[-_\.]?
21+
(?P<post>
22+
post
23+
[-_\.]?
24+
[0-9]+
25+
)
26+
)?
27+
(?: # dev release
28+
[-_\.]?
29+
(?P<dev>
30+
dev
31+
[-_\.]?
32+
[0-9]+
33+
)
34+
)?
35+
)
36+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
37+
"""
38+
PEP440_SERIALIZE_PATTERNS = [
39+
"{major}.{minor}.{patch}{pre_l}{pre_n}.{post}.{dev}+{local}",
40+
"{major}.{minor}.{patch}{pre_l}{pre_n}.{post}.{dev}",
41+
"{major}.{minor}.{patch}{pre_l}{pre_n}.{post}+{local}",
42+
"{major}.{minor}.{patch}{pre_l}{pre_n}.{dev}+{local}",
43+
"{major}.{minor}.{patch}.{post}.{dev}+{local}",
44+
"{major}.{minor}.{patch}{pre_l}{pre_n}.{post}",
45+
"{major}.{minor}.{patch}{pre_l}{pre_n}.{dev}",
46+
"{major}.{minor}.{patch}{pre_l}{pre_n}+{local}",
47+
"{major}.{minor}.{patch}.{post}.{dev}",
48+
"{major}.{minor}.{patch}.{post}+{local}",
49+
"{major}.{minor}.{patch}.{dev}+{local}",
50+
"{major}.{minor}.{patch}.{pre_l}{pre_n}",
51+
"{major}.{minor}.{patch}.{post}",
52+
"{major}.{minor}.{patch}.{dev}",
53+
"{major}.{minor}.{patch}+{local}",
54+
"{major}.{minor}.{patch}",
55+
]
56+
PEP440_COMPONENT_CONFIGS = {
57+
"major": VersionComponentConfig(),
58+
"minor": VersionComponentConfig(),
59+
"patch": VersionComponentConfig(),
60+
"pre_l": VersionComponentConfig(values=["", "a", "b", "rc"]),
61+
"pre_n": VersionComponentConfig(),
62+
"post": VersionComponentConfig(depends_on="patch"),
63+
"dev": VersionComponentConfig(depends_on="patch"),
64+
"local": VersionComponentConfig(depends_on="patch", optional_value=""),
65+
}
66+
67+
68+
def pep440_version_spec() -> VersionSpec:
69+
"""Return a VersionSpec for PEP 440."""
70+
return VersionSpec(components=PEP440_COMPONENT_CONFIGS)
71+
72+
73+
# Adapted from https://regex101.com/r/Ly7O1x/3/
74+
SEMVER_PATTERN = r"""
75+
(?P<major>0|[1-9]\d*)\.
76+
(?P<minor>0|[1-9]\d*)\.
77+
(?P<patch>0|[1-9]\d*)
78+
(?:
79+
- # dash seperator for pre-release section
80+
(?P<pre_l>[a-zA-Z-]+) # pre-release label
81+
(?P<pre_n>0|[1-9]\d*) # pre-release version number
82+
)? # pre-release section is optional
83+
(?:
84+
\+ # plus seperator for build metadata section
85+
(?P<buildmetadata>
86+
[0-9a-zA-Z-]+
87+
(?:\.[0-9a-zA-Z-]+)*
88+
)
89+
)? # build metadata section is optional
90+
"""
91+
SEMVER_SERIALIZE_PATTERNS = [
92+
"{major}.{minor}.{patch}-{pre_l}{pre_n}+{buildmetadata}",
93+
"{major}.{minor}.{patch}-{pre_l}{pre_n}",
94+
"{major}.{minor}.{patch}+{buildmetadata}",
95+
"{major}.{minor}.{patch}",
96+
]
97+
SEMVER_COMPONENT_CONFIGS = {
98+
"major": VersionComponentConfig(),
99+
"minor": VersionComponentConfig(),
100+
"patch": VersionComponentConfig(),
101+
"pre_l": VersionComponentConfig(values=["", "a", "b", "rc"]),
102+
"pre_n": VersionComponentConfig(),
103+
"buildmetadata": VersionComponentConfig(independent=True),
104+
}
105+
106+
107+
def semver_spec() -> VersionSpec:
108+
"""Return a VersionSpec for SEMVER."""
109+
return VersionSpec(components=SEMVER_COMPONENT_CONFIGS)

bumpversion/versioning/models.py

+13-56
Original file line numberDiff line numberDiff line change
@@ -10,61 +10,6 @@
1010
from bumpversion.utils import key_val_string
1111
from bumpversion.versioning.functions import NumericFunction, PartFunction, ValuesFunction
1212

13-
# Adapted from https://regex101.com/r/Ly7O1x/3/
14-
SEMVER_PATTERN = r"""
15-
(?P<major>0|[1-9]\d*)\.
16-
(?P<minor>0|[1-9]\d*)\.
17-
(?P<patch>0|[1-9]\d*)
18-
(?:
19-
- # dash seperator for pre-release section
20-
(?P<pre_l>[a-zA-Z-]+) # pre-release label
21-
(?P<pre_n>0|[1-9]\d*) # pre-release version number
22-
)? # pre-release section is optional
23-
(?:
24-
\+ # plus seperator for build metadata section
25-
(?P<buildmetadata>
26-
[0-9a-zA-Z-]+
27-
(?:\.[0-9a-zA-Z-]+)*
28-
)
29-
)? # build metadata section is optional
30-
"""
31-
32-
# Adapted from https://packaging.python.org/en/latest/specifications/version-specifiers/
33-
PEP440_PATTERN = r"""
34-
v?
35-
(?:
36-
(?:(?P<epoch>[0-9]+)!)? # Optional epoch
37-
(?P<release>
38-
(?P<major>0|[1-9]\d*)\.
39-
(?P<minor>0|[1-9]\d*)\.
40-
(?P<patch>0|[1-9]\d*)
41-
)
42-
(?P<pre> # pre-release
43-
[-_\.]?
44-
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
45-
[-_\.]?
46-
(?P<pre_n>[0-9]+)?
47-
)?
48-
(?P<post> # post release
49-
(?:-(?P<post_n1>[0-9]+))
50-
|
51-
(?:
52-
[-_\.]?
53-
(?P<post_l>post|rev|r)
54-
[-_\.]?
55-
(?P<post_n2>[0-9]+)?
56-
)
57-
)?
58-
(?P<dev> # dev release
59-
[-_\.]?
60-
(?P<dev_l>dev)
61-
[-_\.]?
62-
(?P<dev_n>[0-9]+)?
63-
)?
64-
)
65-
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
66-
"""
67-
6813

6914
class VersionComponent:
7015
"""
@@ -161,6 +106,7 @@ class VersionComponentConfig(BaseModel):
161106
first_value: Union[str, int, None] = None # Optional. Defaults to first value in values
162107
independent: bool = False
163108
# source: Optional[str] = None # Name of environment variable or context variable to use as the source for value
109+
depends_on: Optional[str] = None # The name of the component this component depends on
164110

165111
def generate_component(self, value: Union[str, int, None] = None) -> VersionComponent:
166112
"""Generate a version component from the configuration."""
@@ -192,7 +138,10 @@ def __init__(self, components: Dict[str, VersionComponentConfig], order: Optiona
192138
for component in self.order[1:]:
193139
if self.component_configs[component].independent:
194140
continue
195-
self.dependency_map[previous_component].append(component)
141+
elif self.component_configs[component].depends_on:
142+
self.dependency_map[self.component_configs[component].depends_on].append(component)
143+
else:
144+
self.dependency_map[previous_component].append(component)
196145
previous_component = component
197146

198147
def create_version(self, values: Dict[str, str]) -> "Version":
@@ -227,6 +176,10 @@ def __init__(
227176
self.components = components
228177
self.original = original
229178

179+
def values(self) -> Dict[str, str]:
180+
"""Return the values of the parts."""
181+
return {key: value.value for key, value in self.components.items()}
182+
230183
def __getitem__(self, key: str) -> VersionComponent:
231184
return self.components[key]
232185

@@ -246,6 +199,10 @@ def __eq__(self, other: Any) -> bool:
246199
else False
247200
)
248201

202+
def required_components(self) -> List[str]:
203+
"""Return the names of the parts that are required."""
204+
return [key for key, value in self.components.items() if value.value != value.func.optional_value]
205+
249206
def bump(self, component_name: str) -> "Version":
250207
"""Increase the value of the specified component, reset its dependents, and return a new Version."""
251208
if component_name not in self.components:

tests/fixtures/basic_cfg_expected.txt

+8-4
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,23 @@
3737
'included_paths': [],
3838
'message': 'Bump version: {current_version} → {new_version}',
3939
'parse': '(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?',
40-
'parts': {'major': {'first_value': None,
40+
'parts': {'major': {'depends_on': None,
41+
'first_value': None,
4142
'independent': False,
4243
'optional_value': None,
4344
'values': None},
44-
'minor': {'first_value': None,
45+
'minor': {'depends_on': None,
46+
'first_value': None,
4547
'independent': False,
4648
'optional_value': None,
4749
'values': None},
48-
'patch': {'first_value': None,
50+
'patch': {'depends_on': None,
51+
'first_value': None,
4952
'independent': False,
5053
'optional_value': None,
5154
'values': None},
52-
'release': {'first_value': None,
55+
'release': {'depends_on': None,
56+
'first_value': None,
5357
'independent': False,
5458
'optional_value': 'gamma',
5559
'values': ['dev', 'gamma']}},

tests/fixtures/basic_cfg_expected.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,25 @@ message: "Bump version: {current_version} → {new_version}"
4545
parse: "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?"
4646
parts:
4747
major:
48+
depends_on: null
4849
first_value: null
4950
independent: false
5051
optional_value: null
5152
values: null
5253
minor:
54+
depends_on: null
5355
first_value: null
5456
independent: false
5557
optional_value: null
5658
values: null
5759
patch:
60+
depends_on: null
5861
first_value: null
5962
independent: false
6063
optional_value: null
6164
values: null
6265
release:
66+
depends_on: null
6367
first_value: null
6468
independent: false
6569
optional_value: "gamma"

tests/fixtures/basic_cfg_expected_full.json

+4
Original file line numberDiff line numberDiff line change
@@ -54,24 +54,28 @@
5454
"parse": "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\-(?P<release>[a-z]+))?",
5555
"parts": {
5656
"major": {
57+
"depends_on": null,
5758
"first_value": null,
5859
"independent": false,
5960
"optional_value": null,
6061
"values": null
6162
},
6263
"minor": {
64+
"depends_on": null,
6365
"first_value": null,
6466
"independent": false,
6567
"optional_value": null,
6668
"values": null
6769
},
6870
"patch": {
71+
"depends_on": null,
6972
"first_value": null,
7073
"independent": false,
7174
"optional_value": null,
7275
"values": null
7376
},
7477
"release": {
78+
"depends_on": null,
7579
"first_value": null,
7680
"independent": false,
7781
"optional_value": "gamma",

0 commit comments

Comments
 (0)