| name | Version Scheme Plugins Reference |
| description | Reference guide for implementing version scheme plugins in Hatchling, covering the VersionSchemeInterface, version validation, normalization, and custom version format support. |
Version Scheme Plugins
Version scheme plugins validate and normalize version numbers during bumping operations.
Overview
Version scheme plugins provide custom validation logic when users bump project versions via hatch version. They ensure version transitions follow specific rules (semantic versioning, date-based versioning, etc.).
Core Interface: VersionSchemeInterface
PLUGIN_NAME
Each version scheme must define a string identifier:
class SemanticVersionScheme(VersionSchemeInterface):
PLUGIN_NAME = 'semver'
Users select version schemes via configuration:
[tool.hatch.version]
scheme = "semver"
Essential Methods
update(desired_version, original_version, version_data)
Required: Validate and normalize version bump.
def update(self, desired_version: str, original_version: str, version_data: dict) -> str:
"""
Return a normalized form of the desired version.
When validate_bump property is enabled (default), this method must
verify that the new version is higher than the original version.
Args:
desired_version: The version the user wants to update to
original_version: The current project version
version_data: Additional context data about the version (from version source)
Returns:
str: Normalized/validated version string
Raises:
Exception: If version is invalid or lower than original
"""
from packaging.version import Version
try:
desired = Version(desired_version)
original = Version(original_version)
except Exception as exc:
raise ValueError(f'Invalid version format: {desired_version}') from exc
if desired <= original:
raise ValueError(
f'Version {desired_version} is not higher than current {original_version}'
)
return str(desired)
Configuration & Properties
root (Property)
Project root directory:
@property
def root(self) -> str:
"""The root of the project tree."""
return self._root
config (Property)
Version scheme configuration from [tool.hatch.version]:
@property
def config(self) -> dict:
"""
Scheme configuration from [tool.hatch.version]
Example:
[tool.hatch.version]
scheme = "semver"
validate-bump = true
"""
return self._config
Official Version Scheme
standard
Validates versions follow PEP 440 versioning.
Configuration:
[tool.hatch.version]
scheme = "standard"
validate-bump = true
Features:
- Validates PEP 440 format
- Ensures new version > original version
- Supports pre-releases (alpha, beta, rc)
- Supports local version identifiers
Examples:
1.0.0 → 1.0.1 (valid)
1.0.0 → 1.1.0 (valid)
1.0.0 → 2.0.0 (valid)
1.0.0 → 1.0.0 (invalid - same)
1.0.0 → 0.9.0 (invalid - lower)
1.0.0 → 1.0.0a1 (invalid - lower)
1.0.0a1 → 1.0.0 (valid - final release)
Third-Party Version Schemes
hatch-semver
Semantic versioning with strict validation.
Installation:
[build-system]
requires = ["hatchling", "hatch-semver"]
Configuration:
[tool.hatch.version]
scheme = "semver"
[tool.hatch.version.hatch-semver]
validate-bump = true
Features:
- Enforces semantic versioning (MAJOR.MINOR.PATCH)
- Validates pre-release and build metadata
- Prevents invalid version transitions
Example: Custom Version Scheme
from hatchling.version.scheme.plugin.interface import VersionSchemeInterface
from datetime import datetime
class DateVersionScheme(VersionSchemeInterface):
"""Calendar versioning (YYYY.MM.DD[.MICRO])"""
PLUGIN_NAME = 'calver'
def update(self, desired_version: str, original_version: str, version_data: dict) -> str:
"""
Validate calendar version format.
Valid formats:
- YYYY.MM.DD (e.g., 2024.11.02)
- YYYY.MM.DD.MICRO (e.g., 2024.11.02.1)
"""
from datetime import datetime
parts = desired_version.split('.')
if len(parts) < 3 or len(parts) > 4:
raise ValueError(
f'Calendar version must be YYYY.MM.DD[.MICRO], got {desired_version}'
)
try:
year = int(parts[0])
month = int(parts[1])
day = int(parts[2])
datetime(year, month, day)
if len(parts) == 4:
micro = int(parts[3])
if micro < 0:
ValueError()
ValueError exc:
ValueError() exc
:
desired_date = datetime(year, month, day)
original_parts = original_version.split()
original_date = datetime(
(original_parts[]),
(original_parts[]),
(original_parts[]),
)
desired_date < original_date:
ValueError(
)
desired_date == original_date:
original_micro = (original_parts[]) (original_parts) ==
desired_micro = (parts[]) (parts) ==
desired_micro <= original_micro:
ValueError(
)
(ValueError, IndexError) exc:
ValueError() exc
desired_version
Configuration Examples
Standard Scheme with Validation
[tool.hatch.version]
scheme = "standard"
validate-bump = true
Custom Scheme
[tool.hatch.version]
scheme = "calver"
[tool.hatch.version.calver]
No Validation
[tool.hatch.version]
scheme = "standard"
validate-bump = false
Usage Patterns
Version Bumping with Validation
hatch version 1.1.0
hatch version 0.9.0
hatch version 2024.11.02
hatch version 2024.11.02.1
hatch version 2024.10.31
Semantic Versioning
hatch version major
hatch version minor
hatch version patch
Best Practices
- Clear Error Messages: Explain why version is rejected
- Document Format: Clearly document expected version format
- Consistent Validation: Apply same rules in
update() and documentation
- Handle Edge Cases: Consider pre-releases, local versions, etc.
- Test Thoroughly: Version schemes affect all version bumping
- Be Permissive: Don't unnecessarily restrict valid versions
- Consider Legacy: Support existing version format transitions
Error Handling
Version schemes should raise descriptive exceptions:
def update(self, desired_version: str, original_version: str, version_data: dict) -> str:
if not self._is_valid(desired_version):
raise ValueError(
f'Invalid version format. Expected: {self._format_description}, '
f'got: {desired_version}'
)
if not self._is_higher(desired_version, original_version):
raise ValueError(
f'Version {desired_version} must be higher than {original_version}'
)
return self._normalize(desired_version)
See Also