3
3
from __future__ import annotations
4
4
5
5
from collections import defaultdict , deque
6
- from typing import Any , Dict , List , Optional , Union
6
+ from itertools import chain
7
+ from typing import Any , Dict , List , Optional , Tuple , Union
7
8
8
- from pydantic import BaseModel
9
+ from pydantic import BaseModel , model_validator
9
10
10
11
from bumpversion .exceptions import InvalidVersionPartError
11
12
from bumpversion .utils import key_val_string
@@ -26,13 +27,15 @@ def __init__(
26
27
optional_value : Optional [str ] = None ,
27
28
first_value : Union [str , int , None ] = None ,
28
29
independent : bool = False ,
30
+ always_increment : bool = False ,
29
31
calver_format : Optional [str ] = None ,
30
32
source : Optional [str ] = None ,
31
33
value : Union [str , int , None ] = None ,
32
34
):
33
35
self ._value = str (value ) if value is not None else None
34
36
self .func : Optional [PartFunction ] = None
35
- self .independent = independent
37
+ self .always_increment = always_increment
38
+ self .independent = True if always_increment else independent
36
39
self .source = source
37
40
self .calver_format = calver_format
38
41
if values :
@@ -58,6 +61,7 @@ def copy(self) -> "VersionComponent":
58
61
optional_value = self .func .optional_value ,
59
62
first_value = self .func .first_value ,
60
63
independent = self .independent ,
64
+ always_increment = self .always_increment ,
61
65
calver_format = self .calver_format ,
62
66
source = self .source ,
63
67
value = self ._value ,
@@ -123,20 +127,32 @@ class VersionComponentSpec(BaseModel):
123
127
independent : bool = False
124
128
"""Is the component independent of the other components?"""
125
129
130
+ always_increment : bool = False
131
+ """Should the component always increment, even if it is not necessary?"""
132
+
126
133
calver_format : Optional [str ] = None
127
134
"""The format string for a CalVer component."""
128
135
129
136
# source: Optional[str] = None # Name of environment variable or context variable to use as the source for value
130
137
depends_on : Optional [str ] = None
131
138
"""The name of the component this component depends on."""
132
139
140
+ @model_validator (mode = "before" )
141
+ @classmethod
142
+ def set_always_increment_with_calver (cls , data : Any ) -> Any :
143
+ """Set always_increment to True if calver_format is present."""
144
+ if isinstance (data , dict ) and data .get ("calver_format" ):
145
+ data ["always_increment" ] = True
146
+ return data
147
+
133
148
def create_component (self , value : Union [str , int , None ] = None ) -> VersionComponent :
134
149
"""Generate a version component from the configuration."""
135
150
return VersionComponent (
136
151
values = self .values ,
137
152
optional_value = self .optional_value ,
138
153
first_value = self .first_value ,
139
154
independent = self .independent ,
155
+ always_increment = self .always_increment ,
140
156
calver_format = self .calver_format ,
141
157
# source=self.source,
142
158
value = value ,
@@ -158,6 +174,7 @@ def __init__(self, components: Dict[str, VersionComponentSpec], order: Optional[
158
174
self .order = order
159
175
self .dependency_map = defaultdict (list )
160
176
previous_component = self .order [0 ]
177
+ self .always_increment = [name for name , config in self .component_configs .items () if config .always_increment ]
161
178
for component in self .order [1 :]:
162
179
if self .component_configs [component ].independent :
163
180
continue
@@ -231,12 +248,35 @@ def bump(self, component_name: str) -> "Version":
231
248
if component_name not in self .components :
232
249
raise InvalidVersionPartError (f"No part named { component_name !r} " )
233
250
234
- components_to_reset = self .version_spec .get_dependents (component_name )
235
-
236
251
new_values = dict (self .components .items ())
237
- new_values [component_name ] = self .components [component_name ].bump ()
252
+ always_incr_values , components_to_reset = self ._always_increment ()
253
+ new_values .update (always_incr_values )
254
+
255
+ if component_name not in components_to_reset :
256
+ new_values [component_name ] = self .components [component_name ].bump ()
257
+ components_to_reset |= set (self .version_spec .get_dependents (component_name ))
258
+
238
259
for component in components_to_reset :
239
260
if not self .components [component ].is_independent :
240
261
new_values [component ] = self .components [component ].null ()
241
262
242
263
return Version (self .version_spec , new_values , self .original )
264
+
265
+ def _always_incr_dependencies (self ) -> dict :
266
+ """Return the components that always increment and depend on the given component."""
267
+ return {name : self .version_spec .get_dependents (name ) for name in self .version_spec .always_increment }
268
+
269
+ def _increment_always_incr (self ) -> dict :
270
+ """Increase the values of the components that always increment."""
271
+ components = self .version_spec .always_increment
272
+ return {name : self .components [name ].bump () for name in components }
273
+
274
+ def _always_increment (self ) -> Tuple [dict , set ]:
275
+ """Return the components that always increment and their dependents."""
276
+ values = self ._increment_always_incr ()
277
+ dependents = self ._always_incr_dependencies ()
278
+ for component_name , value in values .items ():
279
+ if value == self .components [component_name ]:
280
+ dependents .pop (component_name , None )
281
+ unique_dependents = set (chain .from_iterable (dependents .values ()))
282
+ return values , unique_dependents
0 commit comments