Coverage for src/git_changelog/versioning.py: 97.44%
181 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-04 17:35 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-04 17:35 +0200
1"""Utilities to handle different versioning schemes such as SemVer and PEP 440."""
3from __future__ import annotations
5from typing import Any, Literal, Protocol
7import semver
8from packaging import version as packaging_version
10SemVerStrategy = Literal[
11 "major",
12 "minor",
13 "patch",
14 "release",
15]
16PEP440Strategy = Literal[
17 "epoch",
18 "release",
19 "major",
20 "minor",
21 "micro",
22 "patch",
23 "pre",
24 "alpha",
25 "beta",
26 "candidate",
27 "post",
28 "dev",
29 "major+alpha",
30 "major+beta",
31 "major+candidate",
32 "major+dev",
33 "major+alpha+dev",
34 "major+beta+dev",
35 "major+candidate+dev",
36 "minor+alpha",
37 "minor+beta",
38 "minor+candidate",
39 "minor+dev",
40 "minor+alpha+dev",
41 "minor+beta+dev",
42 "minor+candidate+dev",
43 "micro+alpha",
44 "micro+beta",
45 "micro+candidate",
46 "micro+dev",
47 "micro+alpha+dev",
48 "micro+beta+dev",
49 "micro+candidate+dev",
50 "alpha+dev",
51 "beta+dev",
52 "candidate+dev",
53]
56_release_kind = {"a": "alpha", "b": "beta", "c": "candidate", "rc": "candidate", "p": "post"}
59class ParsedVersion(Protocol):
60 """Base class for versioning schemes."""
62 def __lt__(self, other: object) -> bool: ... 62 ↛ exitline 62 didn't jump to line 62, because
63 def __le__(self, other: object) -> bool: ... 63 ↛ exitline 63 didn't jump to line 63, because
64 def __eq__(self, other: object) -> bool: ... 64 ↛ exitline 64 didn't jump to line 64, because
65 def __ge__(self, other: object) -> bool: ... 65 ↛ exitline 65 didn't jump to line 65, because
66 def __gt__(self, other: object) -> bool: ... 66 ↛ exitline 66 didn't jump to line 66, because
67 def __ne__(self, other: object) -> bool: ... 67 ↛ exitline 67 didn't jump to line 67, because
70class SemVerVersion(semver.Version, ParsedVersion): # type: ignore[misc]
71 """SemVer version."""
73 def bump_major(self) -> SemVerVersion: # noqa: D102
74 return SemVerVersion(*super().bump_major()) # type: ignore[misc]
76 def bump_minor(self) -> SemVerVersion: # noqa: D102
77 return SemVerVersion(*super().bump_minor()) # type: ignore[misc]
79 def bump_patch(self) -> SemVerVersion: # noqa: D102
80 return SemVerVersion(*super().bump_patch()) # type: ignore[misc]
82 def bump_release(self) -> SemVerVersion:
83 """Bump from a pre-release to the same, regular release.
85 Returns:
86 The same version, without pre-release or build metadata.
87 """
88 return SemVerVersion(self.major, self.minor, self.patch)
91class PEP440Version(packaging_version.Version, ParsedVersion): # type: ignore[misc]
92 """PEP 440 version."""
94 @classmethod
95 def from_parts(
96 cls,
97 epoch: int | None = None,
98 release: tuple[int, ...] | None = None,
99 pre: tuple[str, int] | None = None,
100 post: int | None = None,
101 dev: int | None = None,
102 ) -> PEP440Version:
103 """Build a version from its parts.
105 Parameters:
106 epoch: Version's epoch number.
107 release: Version's release numbers.
108 pre: Version's prerelease kind and number.
109 post: Version's post number.
110 dev: Version's dev number.
112 Returns:
113 A PEP 440 version.
114 """
115 # Since the original class only allows instantiating a version
116 # by passing a string, we first create a dummy version "1"
117 # and then re-assign its internal `_version` with the real one.
118 version = cls("1")
119 version._version = packaging_version._Version(
120 epoch=epoch or 0,
121 release=release or (),
122 pre=pre,
123 post=None if post is None else ("post", post),
124 dev=None if dev is None else ("dev", dev),
125 local=None,
126 )
128 # We also have to update its `_key` attribute.
129 # This is a hack and I would prefer that such functionality
130 # is exposed directly in the original class.
131 version._key = packaging_version._cmpkey(
132 version._version.epoch,
133 version._version.release,
134 version._version.pre,
135 version._version.post,
136 version._version.dev,
137 version._version.local,
138 )
140 return version
142 def bump_epoch(self) -> PEP440Version:
143 """Bump epoch.
145 Examples:
146 >>> PEP440Version("1.0").bump_epoch()
147 <Version('1!1.0')>
148 >>> PEP440Version("0!1.0").bump_epoch()
149 <Version('1!1.0')>
150 >>> PEP440Version("1!1.0").bump_epoch()
151 <Version('2!1.0')>
152 >>> PEP440Version("1.0a2.post3").bump_epoch()
153 <Version('2!1.0')>
155 Returns:
156 Version with bumped epoch, same release, and the right parts reset to 0 or nothing.
157 """
158 return PEP440Version.from_parts(epoch=self.epoch + 1, release=self.release)
160 def bump_release(self, level: int | None = None, *, trim: bool = False) -> PEP440Version:
161 """Bump given release level.
163 Parameters:
164 level: The release level to bump.
166 - 0 means major
167 - 1 means minor
168 - 2 means micro (patch)
169 - 3+ don't have names
170 - None means move from pre-release to release (unchanged)
171 trim: Whether to trim all zeroes on the right after bumping.
173 Examples:
174 >>> PEP440Version("1").bump_release(0)
175 <Version('2')>
176 >>> PEP440Version("1.1").bump_release(0)
177 <Version('2.0')>
178 >>> PEP440Version("1.1.1").bump_release(0)
179 <Version('2.0.0')>
180 >>> PEP440Version("1.1.1").bump_release(0, trim=True)
181 <Version('2')>
182 >>> PEP440Version("1a2.post3").bump_release(0)
183 <Version('2')>
185 >>> PEP440Version("1").bump_release(1)
186 <Version('1.1')>
187 >>> PEP440Version("1.1").bump_release(1)
188 <Version('1.2')>
189 >>> PEP440Version("1.1.1").bump_release(1)
190 <Version('1.2.0')>
191 >>> PEP440Version("1.1.1").bump_release(1, trim=True)
192 <Version('1.2')>
193 >>> PEP440Version("1a2.post3").bump_release(1)
194 <Version('1.1')>
196 >>> PEP440Version("1a0").bump_release()
197 <Version('1')>
198 >>> PEP440Version("1b1").bump_release()
199 <Version('1')>
200 >>> PEP440Version("1rc2").bump_release()
201 <Version('1')>
202 >>> PEP440Version("1a2.dev0").bump_release()
203 <Version('1')>
204 >>> PEP440Version("1post0").bump_release()
205 ValueError: Cannot bump from post-release to release
207 Returns:
208 Version with same epoch, bumped release level, and the right parts reset to 0 or nothing.
209 """
210 release = list(self.release)
212 # When level is not specified, user wants to bump the version
213 # as a "release", going out of alpha/beta/candidate phase.
214 # So we simply keep the release part as it is, optionally trimming it.
215 if level is None:
216 # However if this is a post-release, this is an error:
217 # we can't bump from a post-release to the same, regular release.
218 if self.post is not None:
219 raise ValueError("Cannot bump from post-release to release")
220 if trim:
221 while release[-1] == 0:
222 release.pop()
224 # When level is specified, we bump the specified level.
225 # If the given level is higher that the number of release parts,
226 # we insert the missing parts as 0.
227 else:
228 try:
229 release[level] += 1
230 except IndexError:
231 while len(release) < level:
232 release.append(0)
233 release.append(1)
234 if trim:
235 release = release[: level + 1]
236 else:
237 for index in range(level + 1, len(release)):
238 release[index] = 0
240 # We rebuild the version with same epoch, updated release,
241 # and pre/post/dev parts dropped.
242 return PEP440Version.from_parts(epoch=self.epoch, release=tuple(release))
244 def bump_major(self, *, trim: bool = False) -> PEP440Version:
245 """Bump major.
247 Parameters:
248 trim: Whether to trim all zeroes on the right after bumping.
250 Examples:
251 >>> PEP440Version("1").bump_major()
252 <Version('2')>
253 >>> PEP440Version("1.1").bump_major()
254 <Version('2.0')>
255 >>> PEP440Version("1.1.1").bump_major()
256 <Version('2.0.0')>
257 >>> PEP440Version("1.1.1").bump_major(trim=True)
258 <Version('2')>
259 >>> PEP440Version("1a2.post3").bump_major()
260 <Version('2')>
262 Returns:
263 Version with same epoch, bumped major and the right parts reset to 0 or nothing.
264 """
265 return self.bump_release(level=0, trim=trim)
267 def bump_minor(self, *, trim: bool = False) -> PEP440Version:
268 """Bump minor.
270 Parameters:
271 trim: Whether to trim all zeroes on the right after bumping.
273 Examples:
274 >>> PEP440Version("1").bump_minor()
275 <Version('1.1')>
276 >>> PEP440Version("1.1").bump_minor()
277 <Version('1.2')>
278 >>> PEP440Version("1.1.1").bump_minor()
279 <Version('1.2.0')>
280 >>> PEP440Version("1.1.1").bump_minor(trim=True)
281 <Version('1.2')>
282 >>> PEP440Version("1a2.post3").bump_minor()
283 <Version('1.1')>
285 Returns:
286 Version with same epoch, same major, bumped minor and the right parts reset to 0 or nothing.
287 """
288 return self.bump_release(level=1, trim=trim)
290 def bump_micro(self, *, trim: bool = False) -> PEP440Version:
291 """Bump micro.
293 Parameters:
294 trim: Whether to trim all zeroes on the right after bumping.
296 Examples:
297 >>> PEP440Version("1").bump_micro()
298 <Version('1.0.1')>
299 >>> PEP440Version("1.1").bump_micro()
300 <Version('1.1.1')>
301 >>> PEP440Version("1.1.1").bump_micro()
302 <Version('1.1.2')>
303 >>> PEP440Version("1.1.1").bump_micro()
304 <Version('1.1.2')>
305 >>> PEP440Version("1.1.1.1").bump_micro()
306 <Version('1.1.2.0')>
307 >>> PEP440Version("1.1.1.1").bump_micro(trim=True)
308 <Version('1.1.2')>
309 >>> PEP440Version("1a2.post3").bump_micro()
310 <Version('1.0.1')>
312 Returns:
313 Version with same epoch, same major, same minor, bumped micro and the right parts reset to 0 or nothing.
314 """
315 return self.bump_release(level=2, trim=trim)
317 def bump_pre(self, pre: Literal["a", "b", "c", "rc"] | None = None) -> PEP440Version:
318 """Bump pre-release.
320 Parameters:
321 pre: Kind of pre-release to bump.
323 - a means alpha
324 - b means beta
325 - c or rc means (release) candidate
327 Examples:
328 >>> PEP440Version("1").bump_pre()
329 ValueError: Cannot bump from release to alpha pre-release (use `dent_pre`)
330 >>> PEP440Version("1a0").bump_pre()
331 <Version('1a1')>
332 >>> PEP440Version("1a0").bump_pre("a")
333 <Version('1a1')>
334 >>> PEP440Version("1a0.post0").bump_pre()
335 <Version('1a1')>
336 >>> PEP440Version("1b2").bump_pre("a")
337 ValueError: Cannot bump from beta to alpha pre-release (use `dent_alpha`)
338 >>> PEP440Version("1c2").bump_pre("a")
339 ValueError: Cannot bump from candidate to alpha pre-release (use `dent_alpha`)
341 >>> PEP440Version("1").bump_pre("b")
342 ValueError: Cannot bump from release to beta pre-release (use `dent_beta`)
343 >>> PEP440Version("1a2").bump_pre("b")
344 <Version('1b0')>
345 >>> PEP440Version("1b2").bump_pre("b")
346 <Version('1b3')>
347 >>> PEP440Version("1c2").bump_pre("b")
348 ValueError: Cannot bump from candidate to beta pre-release (use `dent_beta`)
350 >>> PEP440Version("1").bump_pre("c")
351 ValueError: Cannot bump from release to candidate pre-release (use `dent_candidate`)
352 >>> PEP440Version("1a2").bump_pre("c")
353 <Version('1rc0')>
354 >>> PEP440Version("1b2").bump_pre("rc")
355 <Version('1rc0')>
356 >>> PEP440Version("1rc2").bump_pre("c")
357 <Version('1rc3')>
359 Returns:
360 Version with same epoch, same release, bumped pre-release and the right parts reset to 0 or nothing.
361 """
362 if self.pre is None:
363 kind = _release_kind.get(pre, "") # type: ignore[arg-type]
364 raise ValueError(
365 f"Cannot bump from release to {kind + ' ' if kind else ''}pre-release (use `dent_{kind or 'pre'}`)",
366 )
367 current_pre: Literal["a", "b", "c", "rc"]
368 current_pre, number = self.pre # type: ignore[assignment]
369 if pre is None:
370 pre = current_pre
371 if pre == current_pre:
372 number += 1
373 elif current_pre < pre:
374 number = 0
375 else:
376 raise ValueError(
377 f"Cannot bump from {_release_kind.get(current_pre, 'release')} to {_release_kind[pre]} pre-release (use `dent_{_release_kind[pre]}`)",
378 )
379 return PEP440Version.from_parts(epoch=self.epoch, release=self.release, pre=(pre, number))
381 def bump_alpha(self) -> PEP440Version:
382 """Bump alpha-release.
384 Examples:
385 >>> PEP440Version("1").bump_alpha()
386 ValueError: Cannot bump from release to alpha pre-release (use `dent_alpha`)
387 >>> PEP440Version("1a0").bump_alpha()
388 <Version('1a1')>
389 >>> PEP440Version("1a0").bump_alpha("a")
390 <Version('1a1')>
391 >>> PEP440Version("1a0.post0").bump_alpha()
392 <Version('1a1')>
394 Returns:
395 Version with same epoch, same release, bumped alpha pre-release and the right parts reset to 0 or nothing.
396 """
397 return self.bump_pre("a")
399 def bump_beta(self) -> PEP440Version:
400 """Bump beta-release.
402 Examples:
403 >>> PEP440Version("1").bump_beta()
404 ValueError: Cannot bump from release to beta pre-release (use `dent_beta`)
405 >>> PEP440Version("1b0").bump_beta()
406 <Version('1b1')>
407 >>> PEP440Version("1b0").bump_beta()
408 <Version('1b1')>
409 >>> PEP440Version("1b0.post0").bump_beta()
410 <Version('1b1')>
412 Returns:
413 Version with same epoch, same release, bumped beta pre-release and the right parts reset to 0 or nothing.
414 """
415 return self.bump_pre("b")
417 def bump_candidate(self) -> PEP440Version:
418 """Bump candidate release.
420 Examples:
421 >>> PEP440Version("1").bump_candidate()
422 ValueError: Cannot bump from release to candidate pre-release (use `dent_candidate`)
423 >>> PEP440Version("1c0").bump_candidate()
424 <Version('1rc1')>
425 >>> PEP440Version("1c0").bump_candidate()
426 <Version('1rc1')>
427 >>> PEP440Version("1c0.post0").bump_candidate()
428 <Version('1rc1')>
430 Returns:
431 Version with same epoch, same release, bumped candidate pre-release and the right parts reset to 0 or nothing.
432 """
433 return self.bump_pre("rc")
435 def bump_post(self) -> PEP440Version:
436 """Bump post-release.
438 Examples:
439 >>> PEP440Version("1").bump_post()
440 <Version('1.post0')>
441 >>> PEP440Version("1.post0").bump_post()
442 <Version('1.post1')>
443 >>> PEP440Version("1a0.post0").bump_post()
444 <Version('1a0.post1')>
445 >>> PEP440Version("1.post0.dev1").bump_post()
446 <Version('1.post1')>
448 Returns:
449 Version with same epoch, same release, same pre-release, bumped post-release and the right parts reset to 0 or nothing.
450 """
451 post = 0 if self.post is None else self.post + 1
452 return PEP440Version.from_parts(epoch=self.epoch, release=self.release, pre=self.pre, post=post)
454 def bump_dev(self) -> PEP440Version:
455 """Bump dev-release.
457 Examples:
458 >>> PEP440Version("1").bump_dev()
459 ValueError: Cannot bump from release to dev-release (use `dent_dev`)
460 >>> PEP440Version("1a0").bump_dev()
461 ValueError: Cannot bump from alpha to dev-release (use `dent_dev`)
462 >>> PEP440Version("1b1").bump_dev()
463 ValueError: Cannot bump from beta to dev-release (use `dent_dev`)
464 >>> PEP440Version("1rc2").bump_dev()
465 ValueError: Cannot bump from candidate to dev-release (use `dent_dev`)
466 >>> PEP440Version("1.post0").bump_dev()
467 ValueError: Cannot bump from post to dev-release (use `dent_dev`)
468 >>> PEP440Version("1a0.dev1").bump_dev()
469 <Version('1a0.dev2')>
471 Returns:
472 Version with same epoch, same release, same pre-release, same post-release and bumped dev-release.
473 """
474 if self.dev is None:
475 if self.post is not None:
476 kind = "p"
477 elif self.pre is not None:
478 kind = self.pre[0]
479 else:
480 kind = "z"
481 raise ValueError(f"Cannot bump from {_release_kind.get(kind, 'release')} to dev-release (use `dent_dev`)")
482 return PEP440Version.from_parts(
483 epoch=self.epoch,
484 release=self.release,
485 pre=self.pre,
486 post=self.post,
487 dev=self.dev + 1,
488 )
490 def dent_pre(self, pre: Literal["a", "b", "c", "rc"] | None = None) -> PEP440Version:
491 """Dent to pre-release.
493 This method dents a release down to an alpha, beta or candidate pre-release.
495 Parameters:
496 pre: Kind of pre-release to bump.
498 - a means alpha
499 - b means beta
500 - c or rc means (release) candidate
502 Examples:
503 >>> PEP440Version("1").dent_pre()
504 <Version('1a0')>
505 >>> PEP440Version("1").dent_pre("a")
506 <Version('1a0')>
507 >>> PEP440Version("1a0").dent_pre("a")
508 ValueError: Cannot dent alpha pre-releases
509 >>> PEP440Version("1").dent_pre("b")
510 <Version('1b0')>
511 >>> PEP440Version("1b0").dent_pre("b")
512 ValueError: Cannot dent beta pre-releases
513 >>> PEP440Version("1").dent_pre("c")
514 <Version('1rc0')>
515 >>> PEP440Version("1").dent_pre("rc")
516 <Version('1rc0')>
517 >>> PEP440Version("1rc0").dent_pre("c")
518 ValueError: Cannot dent candidate pre-releases
520 Returns:
521 Version with same epoch and release dented to pre-release.
522 """
523 if self.pre is not None:
524 raise ValueError(f"Cannot dent {_release_kind[self.pre[0]]} pre-releases")
525 if pre is None:
526 pre = "a"
527 return PEP440Version.from_parts(epoch=self.epoch, release=self.release, pre=(pre, 0))
529 def dent_alpha(self) -> PEP440Version:
530 """Dent to alpha-release.
532 Examples:
533 >>> PEP440Version("1").dent_alpha()
534 <Version('1a0')>
535 >>> PEP440Version("1a0").dent_alpha()
536 ValueError: Cannot dent alpha pre-releases
537 >>> PEP440Version("1b0").dent_alpha()
538 ValueError: Cannot dent beta pre-releases
539 >>> PEP440Version("1rc0").dent_alpha()
540 ValueError: Cannot dent candidate pre-releases
542 Returns:
543 Version with same epoch and release dented to alpha pre-release.
544 """
545 return self.dent_pre("a")
547 def dent_beta(self) -> PEP440Version:
548 """Dent to beta-release.
550 Examples:
551 >>> PEP440Version("1").dent_beta()
552 <Version('1b0')>
553 >>> PEP440Version("1a0").dent_beta()
554 ValueError: Cannot dent alpha pre-releases
555 >>> PEP440Version("1b0").dent_beta()
556 ValueError: Cannot dent beta pre-releases
557 >>> PEP440Version("1rc0").dent_beta()
558 ValueError: Cannot dent candidate pre-releases
560 Returns:
561 Version with same epoch and release dented to beta pre-release.
562 """
563 return self.dent_pre("b")
565 def dent_candidate(self) -> PEP440Version:
566 """Dent to candidate release.
568 Examples:
569 >>> PEP440Version("1").dent_candidate()
570 <Version('1rc0')>
571 >>> PEP440Version("1a0").dent_candidate()
572 ValueError: Cannot dent alpha pre-releases
573 >>> PEP440Version("1b0").dent_candidate()
574 ValueError: Cannot dent beta pre-releases
575 >>> PEP440Version("1rc0").dent_candidate()
576 ValueError: Cannot dent candidate pre-releases
578 Returns:
579 Version with same epoch and release dented to candidate pre-release.
580 """
581 return self.dent_pre("rc")
583 def dent_dev(self) -> PEP440Version:
584 """Dent to dev-release.
586 Examples:
587 >>> PEP440Version("1").dent_dev()
588 <Version('1.dev0')>
589 >>> PEP440Version("1a0").dent_dev()
590 <Version('1a0.dev0')>
591 >>> PEP440Version("1b1").dent_dev()
592 <Version('1b1.dev0')>
593 >>> PEP440Version("1c2").dent_dev()
594 <Version('1rc2.dev0')>
595 >>> PEP440Version("1.post0").dent_dev()
596 <Version('1.post0.dev0')>
597 >>> PEP440Version("1a0.dev1").dent_dev()
598 ValueError: Cannot dent dev-releases
600 Returns:
601 Version with same epoch and release dented to dev-release.
602 """
603 if self.dev is not None:
604 raise ValueError("Cannot dent dev-releases")
605 return PEP440Version.from_parts(epoch=self.epoch, release=self.release, pre=self.pre, post=self.post, dev=0)
608def version_prefix(version: str) -> tuple[str, str]:
609 """Return a version and its optional `v` prefix.
611 Arguments:
612 version: The full version.
614 Returns:
615 version: The version without its prefix.
616 prefix: The version prefix.
617 """
618 prefix = ""
619 if version[0] == "v":
620 prefix = "v"
621 version = version[1:]
622 return version, prefix
625def parse_semver(version: str) -> tuple[SemVerVersion, str]:
626 """Parse a SemVer version.
628 Returns:
629 A semver version instance with useful methods.
630 """
631 version, prefix = version_prefix(version)
632 return SemVerVersion.parse(version), prefix
635def parse_pep440(version: str) -> tuple[PEP440Version, str]:
636 """Parse a PEP version.
638 Returns:
639 A PEP 440 version instance with useful methods.
640 """
641 version, prefix = version_prefix(version)
642 return PEP440Version(version), prefix
645class VersionBumper:
646 """Base class for version bumpers."""
648 initial: str
650 def __init__(self, strategies: tuple[str, ...]) -> None:
651 """Initialize the bumper.
653 Parameters:
654 strategies: The supported bumping strategies.
655 """
656 self.strategies = strategies
658 def __call__(self, version: str, strategy: str = ..., **kwargs: Any) -> str:
659 """Bump a version.
661 Parameters:
662 version: The version to bump.
663 strategy: The bumping strategy.
664 **kwargs: Additional bumper-specific arguments.
666 Returns:
667 The bumped version.
668 """
669 raise NotImplementedError
672class PEP440Bumper(VersionBumper):
673 """PEP 440 version bumper."""
675 initial: str = "0.0.0"
677 def __call__( # type: ignore[override]
678 self,
679 version: str,
680 strategy: PEP440Strategy = "micro",
681 *,
682 zerover: bool = False,
683 trim: bool = False,
684 ) -> str:
685 """Bump a PEP 440 version.
687 Arguments:
688 version: The version to bump.
689 strategy: The part of the version to bump.
690 zerover: Keep major version at zero, even for breaking changes.
691 trim: Whether to trim all zeroes on the right after bumping.
693 Returns:
694 The bumped version.
695 """
696 pep440_version, prefix = parse_pep440(version)
698 # Split into main part and pre/dev markers
699 # (+alpha, +beta, +candidate, +dev).
700 main_part, *predev = strategy.split("+")
702 # Bump main part.
703 if main_part == "epoch":
704 pep440_version = pep440_version.bump_epoch()
705 elif main_part == "release":
706 pep440_version = pep440_version.bump_release(trim=trim)
707 elif main_part == "major":
708 # If major version is 0 and zerover is active, only bump minor.
709 if pep440_version.major == 0 and zerover:
710 pep440_version = pep440_version.bump_minor(trim=trim)
711 else:
712 pep440_version = pep440_version.bump_major(trim=trim)
713 elif main_part == "minor":
714 pep440_version = pep440_version.bump_minor(trim=trim)
715 elif main_part in ("micro", "patch"):
716 pep440_version = pep440_version.bump_micro(trim=trim)
717 elif main_part == "pre":
718 pep440_version = pep440_version.bump_pre()
719 elif main_part == "alpha":
720 pep440_version = pep440_version.bump_alpha()
721 elif main_part == "beta":
722 pep440_version = pep440_version.bump_beta()
723 elif main_part == "candidate":
724 pep440_version = pep440_version.bump_candidate()
725 elif main_part == "post":
726 pep440_version = pep440_version.bump_post()
727 elif main_part == "dev":
728 pep440_version = pep440_version.bump_dev()
729 else:
730 raise ValueError(f"Invalid strategy {main_part}, use one of {', '.join(self.strategies)}")
732 # Dent to pre-release (+alpha, +beta, +candidate).
733 if "alpha" in predev:
734 pep440_version = pep440_version.dent_alpha()
735 elif "beta" in predev:
736 pep440_version = pep440_version.dent_beta()
737 elif "candidate" in predev:
738 pep440_version = pep440_version.dent_candidate()
740 # Dent to dev-release (+dev).
741 if "dev" in predev:
742 pep440_version = pep440_version.dent_dev()
744 # Return new version with preserved prefix.
745 return prefix + str(pep440_version)
748class SemVerBumper(VersionBumper):
749 """SemVer version bumper."""
751 initial: str = "0.0.0"
753 def __call__( # type: ignore[override]
754 self,
755 version: str,
756 strategy: SemVerStrategy = "patch",
757 *,
758 zerover: bool = True,
759 ) -> str:
760 """Bump a SemVer version.
762 Arguments:
763 version: The version to bump.
764 part: The part of the version to bump.
765 zerover: Keep major version at zero, even for breaking changes.
767 Returns:
768 The bumped version.
769 """
770 semver_version, prefix = parse_semver(version)
771 if strategy == "major":
772 if semver_version.major == 0 and zerover:
773 semver_version = semver_version.bump_minor()
774 else:
775 semver_version = semver_version.bump_major()
776 elif strategy == "minor":
777 semver_version = semver_version.bump_minor()
778 elif strategy == "patch":
779 semver_version = semver_version.bump_patch()
780 elif strategy == "release":
781 semver_version = semver_version.bump_release()
782 else:
783 raise ValueError(f"Invalid strategy {strategy}, use one of {', '.join(self.strategies)}")
784 return prefix + str(semver_version)
787bump_pep440 = PEP440Bumper(PEP440Strategy.__args__) # type: ignore[attr-defined]
788"""Bump a PEP 440 version."""
789bump_semver = SemVerBumper(SemVerStrategy.__args__) # type: ignore[attr-defined]
790"""Bump a SemVer version."""