Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ version.next_minor() # 1.11.0
version.next_major() # 2.0.0
```

To bump to a new prerelease version:
To bump to a new developmental or prerelease version:

```python
from pep440_version_utils import Version
Expand All @@ -45,10 +45,12 @@ version = Version("1.10.2")
version.next_alpha() # 1.10.3a1
version.next_beta() # 1.10.3b1
version.next_release_candidate() # 1.10.3rc1
version.next_dev() # 1.10.3.dev1

version.next_alpha("minor") # 1.11.0a1
version.next_beta("mior") # 1.11.0b1
version.next_beta("minor") # 1.11.0b1
version.next_release_candidate("major") # 2.0.0rc1
version.next_dev("major") # 2.0.0.dev1
```

And it implements the full release cycle:
Expand All @@ -65,7 +67,24 @@ rc2 = rc1.next_release_candidate() # 1.10.3rc2
new_version = rc2.next_micro() # 1.10.3
```

Complete with support for developmental versions, including within
prerelease phases:

```python
from pep440_version_utils import Version

version = Version("1.10.2")
dev1 = version.next_dev() # 1.10.3.dev1
alpha1 = dev1.next_alpha() # 1.10.3a1
beta1 = alpha1.next_beta() # 1.10.3b1
rc1 = beta1.next_release_candidate() # 1.10.3rc1
rc2_dev1 = rc1.next_dev() # 1.10.3rc2.dev1
rc2 = rc2_dev1.next_release_candidate() # 1.10.3rc2
new_version = rc2.next_micro() # 1.10.3
```

You can also check if a version is a specific type of prerelease:

```python
from pep440_version_utils import Version

Expand All @@ -76,7 +95,7 @@ Version("1.10.2rc1").is_release_candidate # True

## Limitations

This package doesn't support _post_, _dev_ and _local_ versions yet. **Contributions are welcome 😊**
This package doesn't support _post_, versions yet and has limited support for _local_ versions. **Contributions are welcome 😊**

## How to contribute

Expand Down
140 changes: 118 additions & 22 deletions pep440_version_utils/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ALPHA_SEGMENT = "a"
BETA_SEGMENT = "b"
RC_SEGMENT = "rc"
DEV_SEGMENT = "dev"

VERSION_MICRO = "micro"
VERSION_MINOR = "minor"
Expand Down Expand Up @@ -39,7 +40,7 @@ def next_major(self) -> "Version":
"""
version = copy(self)
major = version.major + 1
if version.pre and not version.minor and not version.micro:
if not version.is_release and not (version.minor or version.micro):
major = version.major
version._version = VersionNamedTuple(
epoch=version._version.epoch,
Expand All @@ -58,7 +59,7 @@ def next_minor(self) -> "Version":
"""
version = copy(self)
minor = version.minor + 1
if version.pre and not version.micro:
if not version.is_release and not version.micro:
minor = version.minor
version._version = VersionNamedTuple(
epoch=version._version.epoch,
Expand All @@ -77,7 +78,7 @@ def next_micro(self) -> "Version":
"""
version = copy(self)
micro = version.micro + 1
if version.pre and version.micro > 0:
if not version.is_release and version.micro > 0:
micro = version.micro
version._version = VersionNamedTuple(
epoch=version._version.epoch,
Expand All @@ -90,6 +91,14 @@ def next_micro(self) -> "Version":
_reset_sort_key(version)
return version

def next_dev(self, version_bump=VERSION_MICRO) -> "Version":
"""
Return a new `Version` with the next developmental version.
Dev is a segment in a release, prerelease of postrelease
defined in PEP440.
"""
return _next_devrelease_version(self, version_bump)

def next_alpha(self, version_bump=VERSION_MICRO) -> "Version":
"""
Return a new `Version` with the next alpha version.
Expand Down Expand Up @@ -132,49 +141,136 @@ def is_release_candidate(self) -> bool:
"""
return self.pre is not None and self.pre[0] == RC_SEGMENT

@property
def is_release(self) -> bool:
"""
Return True if the `Version` is a final release.
"""
return self.public == self.base_version


def _next_prerelease_version(
version: Version, version_bump: Text, segment: Text
def _build_suffixed_version(
version: Version,
prerelease: Optional[Tuple[Text, int]],
devrelease: Optional[Tuple[Text, int]],
) -> Version:
"""
Return a new `Version` with the next prerelease (one of alpha, beta, rc).
It can bump either the major, minor or micro part of the release, if this
is the first prerelease (prerelease is absent from the current version).
Return a new `Version` including the provided prerelease and optional devrelease.
"""
version = copy(version)
if not version.pre:
if version_bump == VERSION_MAJOR:
version = version.next_major()
elif version_bump == VERSION_MINOR:
version = version.next_minor()
elif version_bump == VERSION_MICRO:
version = version.next_micro()
else:
# would use typing.Literal but only available in Python 3.8
raise TypeError(f"Unknown version bump: {version_bump}")

version._version = VersionNamedTuple(
epoch=version._version.epoch,
release=version.release,
pre=_increment_prerelease(version.pre, segment),
pre=prerelease,
post=None,
dev=None,
dev=devrelease,
local=None,
)
_reset_sort_key(version)
return version


def _next_devrelease_version(version: Version, version_bump: Text) -> Version:
"""
Return a new `Version` with the next developmental release (.dev)
Dev releases can be within final, pre- or post-release release phases.
It can bump either the major, minor or micro part of the release if there
the current version has no pre-release, post-release or developmental markers.
"""
version = copy(version)

if version.is_release:
version = _next_release_version(version, version_bump)

prerelease = None
if version.pre is not None:
if version.is_devrelease:
prerelease = version.pre
else:
prerelease_phase = version.pre[0] # increment the current pre-release phase
prerelease = _increment_prerelease(version.pre, prerelease_phase)
devrelease_id = version.dev # NOTE: Incorrect type annotation in BaseVersion
devrelease = _increment_devrelease(devrelease_id) # type: ignore

return _build_suffixed_version(version, prerelease, devrelease)


def _next_prerelease_version(
version: Version, version_bump: Text, segment: Text
) -> Version:
"""
Return a new `Version` with the next prerelease (one of alpha, beta, rc).
It can bump either the major, minor or micro part of the release, if
neither a developmental release or a prerelease is being incremented (the
current version has no dev or prerelease markers).
"""
version = copy(version)

if version.is_release:
version = _next_release_version(version, version_bump)

devrelease = None # The next prerelease version will never be a devrelease
if version.is_devrelease and (version.pre and version.pre[0] == segment):
# If it's a dev release of the next prerelease, we don't increment
prerelease = version.pre
else:
prerelease = _increment_prerelease(version.pre, segment)

return _build_suffixed_version(version, prerelease, devrelease)


def _next_release_version(version: Version, version_bump: Text) -> Version:
"""
Return a new `Version` with the release segment incremented if valid.
The major, minor or micro part of the release segment will be incremented as
specified by `version_bump`, and only if the current version is a final release (not
a developmental, pre or post release).
"""
if version_bump == VERSION_MAJOR:
version = version.next_major()
elif version_bump == VERSION_MINOR:
version = version.next_minor()
elif version_bump == VERSION_MICRO:
version = version.next_micro()
else:
# would use typing.Literal but only available in Python 3.8
raise TypeError(f"Unknown version bump: {version_bump}")
return version


def _increment_devrelease(devrelease_id: Optional[int]) -> Tuple[Text, int]:
"""
Increment a developmental release.
"""
current_tuple = None
if devrelease_id:
current_tuple = (DEV_SEGMENT, devrelease_id)
return _increment_subrelease(current_tuple, DEV_SEGMENT)


def _increment_prerelease(
prerelease: Optional[Tuple[Text, int]], segment: Text
) -> Tuple[Text, int]:
"""
Increment a prerelease tuple.
"""
if not prerelease:
return _increment_subrelease(prerelease, segment)


def _increment_subrelease(
type_number_pair: Optional[Tuple[Text, int]], segment: Text
) -> Tuple[Text, int]:
"""
Increment a part of release (pre, dev or post segment).

The tuple is a pair of the phase/type and the release number.
This is a generalised form of the `BaseVersion.pre` value.
"""
if not type_number_pair:
return (segment, 1)

current_segment, number = prerelease
current_segment, number = type_number_pair
if current_segment == segment:
return (current_segment, number + 1)

Expand Down
Loading