Dynamic Version Sources Overview
Dynamic versioning in Hatchling allows projects to determine versions at build time from external sources rather than hardcoding in pyproject.toml. This approach enables automated version management, integration with version control systems, and flexible deployment workflows.
Configuration Basics
To use dynamic versioning, declare version as dynamic in your project metadata:
[project]
name = "my-package"
dynamic = ["version"]
[tool.hatch.version]
source = "regex" # Version source plugin to use
# Additional configuration specific to the source
Available Version Sources
Hatchling provides several built-in version source plugins:
| Source | Description | Use Case |
|---|---|---|
regex |
Extract version using regular expressions | Default; simple file-based versioning |
code |
Import and execute Python code | Complex version logic or multiple sources |
env |
Read from environment variables | CI/CD integration, containerized deployments |
Third-party sources are also available:
| Source | Description | Installation |
|---|---|---|
vcs |
Version from VCS tags (Git, Mercurial) | pip install hatch-vcs |
nodejs |
Read from package.json | pip install hatch-nodejs-version |
Version Source Plugin Interface
All version source plugins implement the VersionSourceInterface:
class VersionSourceInterface:
"""Interface for version source plugins."""
PLUGIN_NAME: str # Unique identifier for the plugin
root: Path # Project root directory
config: dict # Configuration from pyproject.toml
def get_version_data(self) -> dict:
"""
Retrieve version information.
Returns:
dict: Must contain 'version' key with version string
"""
pass
def set_version(self, version: str) -> None:
"""
Update the version in the source.
Args:
version: New version string to set
"""
pass
Choosing a Version Source
Decision Tree
Is your version in a Python file?
├─ Yes → Is it a simple string assignment?
│ ├─ Yes → Use `regex` source
│ └─ No → Use `code` source
└─ No → Is it in environment/CI?
├─ Yes → Use `env` source
└─ No → Consider third-party sources (vcs, nodejs, etc.)
Comparison Matrix
| Feature | regex |
code |
env |
|---|---|---|---|
| Setup Complexity | Low | Medium | Low |
| Performance | Fast | Slower | Fast |
| Flexibility | Medium | High | Low |
| CI/CD Integration | Good | Good | Excellent |
| Version Bumping | Yes | Yes | External |
| Multiple Files | No | Yes | N/A |
| Custom Logic | No | Yes | No |
Common Configuration Patterns
Single Version File
Most projects use a dedicated version file:
[tool.hatch.version]
source = "regex"
path = "src/my_package/__about__.py"
With __about__.py:
__version__ = "1.2.3"
Version from Package init
Store version in the main package module:
[tool.hatch.version]
source = "regex"
path = "src/my_package/__init__.py"
pattern = "__version__ = ['\"](?P<version>[^'\"]+)['\"]"
Environment-Based Versioning
For containerized or CI/CD workflows:
[tool.hatch.version]
source = "env"
variable = "PROJECT_VERSION"
default = "0.0.0+unknown"
Complex Version Logic
For versions derived from multiple sources:
[tool.hatch.version]
source = "code"
path = "version_helper.py"
expression = "get_version()"
Version Data Structure
Version source plugins return a dictionary that must contain at minimum:
{
"version": "1.2.3" # Required: The version string
}
Some sources may return additional metadata:
{
"version": "1.2.3",
"raw_version": "v1.2.3", # Original format
"source_file": "/path/to/file", # Where version was found
"timestamp": "2024-01-01T00:00:00Z" # When determined
}
Integration with Build Process
Build-Time Resolution
Dynamic versions are resolved during:
- Package Building: When creating wheels or source distributions
- Editable Installs: When installing in development mode
- Metadata Generation: When tools request project metadata
- Version Commands: When using
hatch versionCLI
Caching Behavior
Hatchling caches resolved versions during a build session to ensure consistency:
# Version is resolved once per build
building wheel... # Resolves version
building sdist... # Uses cached version
Working with Dynamic Versions
Displaying Current Version
$ hatch version
1.2.3
Updating Dynamic Versions
The update capability depends on the source plugin:
# For sources that support setting (regex, code)
$ hatch version patch
Old: 1.2.3
New: 1.2.4
# For read-only sources (env)
$ hatch version patch
Error: The 'env' source does not support setting versions
Fallback Strategies
Default Values
Some sources support fallback values:
[tool.hatch.version]
source = "env"
variable = "VERSION"
default = "0.0.0+dev" # Used if variable not set
Error Handling
Configure behavior when version cannot be determined:
[tool.hatch.version]
source = "regex"
path = "src/version.py"
strict = false # Don't fail if file missing (uses 0.0.0)
Metadata Hooks
For advanced scenarios, use metadata hooks to modify version data:
# hatch_build.py
from hatchling.metadata.plugin.interface import MetadataHookInterface
class CustomMetadataHook(MetadataHookInterface):
def update(self, metadata):
# Modify version after source resolution
version = metadata.get("version", "0.0.0")
metadata["version"] = f"{version}+custom"
Performance Considerations
Resolution Timing
- Build Time: Version resolved when package is built
- Not at Import: Version isn't resolved when package is imported
- Development Mode: Resolved once during editable install
Optimization Tips
- Use regex over code when possible (faster)
- Cache expensive operations in code source
- Avoid network calls in version resolution
- Keep version files small for quick parsing
Migration Guide
From setuptools-scm
# Old: setuptools-scm
[build-system]
requires = ["setuptools>=45", "setuptools-scm[toml]>=6.2"]
[tool.setuptools_scm]
# New: hatch-vcs
[build-system]
requires = ["hatchling", "hatch-vcs"]
[tool.hatch.version]
source = "vcs"
From Static to Dynamic
# Old: Static
[project]
version = "1.2.3"
# New: Dynamic
[project]
dynamic = ["version"]
[tool.hatch.version]
source = "regex"
path = "src/my_package/__about__.py"
Troubleshooting
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Version not found | File doesn't exist or pattern doesn't match | Check path and pattern configuration |
| Import errors | Circular dependencies in code source | Isolate version logic from main package |
| Environment variable not set | Missing in environment | Provide default value |
| Version mismatch | Multiple version sources | Ensure single source of truth |
Debugging
Enable verbose output to debug version resolution:
$ hatch version -v
Looking for version in src/my_package/__about__.py
Pattern: __version__ = ["']([^"']+)["']
Found: 1.2.3
Best Practices
- Single Source of Truth: Define version in one place only
- Simple Patterns: Use straightforward regex patterns
- Fast Resolution: Avoid expensive operations during version resolution
- Clear Errors: Provide helpful error messages when version not found
- Document Choice: Explain version source selection in README
- Test Resolution: Verify version resolution in CI/CD
- Provide Fallbacks: Include default values for resilience