Environment Version Source
The environment version source plugin retrieves project versions from environment variables. This approach is particularly useful for CI/CD pipelines, containerized deployments, and build systems where versions are determined externally.
Basic Configuration
Configure the env source in your pyproject.toml:
[project]
name = "my-package"
dynamic = ["version"]
[tool.hatch.version]
source = "env"
variable = "MY_PROJECT_VERSION"
Configuration Options
Required Options
| Option | Type | Description |
|---|---|---|
variable |
string | Name of the environment variable containing the version |
Optional Options
| Option | Type | Default | Description |
|---|---|---|---|
default |
string | None | Default version if variable is not set |
Usage Patterns
Simple Environment Variable
Basic usage with a single environment variable:
[tool.hatch.version]
source = "env"
variable = "VERSION"
# Set the version
export VERSION="1.2.3"
# Build the package
hatch build
With Default Value
Provide a fallback when the variable isn't set:
[tool.hatch.version]
source = "env"
variable = "PACKAGE_VERSION"
default = "0.0.0+dev"
CI/CD Specific Variables
Use CI/CD system variables directly:
# GitHub Actions
[tool.hatch.version]
source = "env"
variable = "GITHUB_REF_NAME" # Uses git tag as version
# GitLab CI
[tool.hatch.version]
source = "env"
variable = "CI_COMMIT_TAG" # Uses git tag as version
# Jenkins
[tool.hatch.version]
source = "env"
variable = "BUILD_VERSION" # Custom Jenkins variable
CI/CD Integration Examples
GitHub Actions
# .github/workflows/release.yml
name: Release
on:
push:
tags:
- "v*"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set version from tag
run: |
# Remove 'v' prefix from tag
echo "PACKAGE_VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
- name: Build package
run: |
pip install hatch
hatch build
- name: Publish to PyPI
run: |
hatch publish
# pyproject.toml
[tool.hatch.version]
source = "env"
variable = "PACKAGE_VERSION"
default = "0.0.0+dev"
GitLab CI
# .gitlab-ci.yml
stages:
- build
- deploy
variables:
PROJECT_VERSION: ${CI_COMMIT_TAG}
build:
stage: build
script:
- pip install hatch
- hatch build
only:
- tags
deploy:
stage: deploy
script:
- hatch publish
only:
- tags
# pyproject.toml
[tool.hatch.version]
source = "env"
variable = "PROJECT_VERSION"
Jenkins
// Jenkinsfile
pipeline {
agent any
environment {
BUILD_VERSION = "${env.TAG_NAME ?: '0.0.0+ci.' + env.BUILD_NUMBER}"
}
stages {
stage('Build') {
steps {
sh '''
pip install hatch
hatch build
'''
}
}
stage('Publish') {
when {
tag pattern: "v\\d+\\.\\d+\\.\\d+", comparator: "REGEXP"
}
steps {
sh 'hatch publish'
}
}
}
}
# pyproject.toml
[tool.hatch.version]
source = "env"
variable = "BUILD_VERSION"
Docker Integration
Dockerfile with Build Args
# Dockerfile
FROM python:3.11-slim
# Accept version as build argument
ARG VERSION=0.0.0+docker
ENV PACKAGE_VERSION=$VERSION
WORKDIR /app
COPY . .
# Install and build
RUN pip install hatch && \
hatch build && \
pip install dist/*.whl
CMD ["python", "-m", "my_package"]
# Build with specific version
docker build --build-arg VERSION=1.2.3 -t my-app:1.2.3 .
Docker Compose
# docker-compose.yml
version: "3.8"
services:
app:
build:
context: .
args:
VERSION: ${VERSION:-0.0.0+dev}
environment:
- PACKAGE_VERSION=${VERSION:-0.0.0+dev}
# Run with version
VERSION=1.2.3 docker-compose up
Development Workflows
Local Development
Set up development environment with custom version:
# .env.local
export PACKAGE_VERSION="0.0.0+dev.$(git rev-parse --short HEAD)"
# Load environment
source .env.local
# Develop with current version
hatch run python -c "import my_package; print(my_package.__version__)"
# Output: 0.0.0+dev.abc1234
Multiple Environments
Use different variables for different environments:
[tool.hatch.version]
source = "env"
variable = "VERSION"
default = "0.0.0+unknown"
# Development
VERSION="0.0.0+dev" hatch build
# Staging
VERSION="1.2.3-rc1" hatch build
# Production
VERSION="1.2.3" hatch build
Version Formatting
Cleaning Version Strings
Sometimes environment variables need processing:
# When variable contains 'v' prefix
[tool.hatch.version]
source = "env"
variable = "GIT_TAG" # e.g., "v1.2.3"
Use a wrapper script to clean:
#!/bin/bash
# build.sh
export CLEAN_VERSION="${GIT_TAG#v}" # Remove 'v' prefix
export PACKAGE_VERSION="$CLEAN_VERSION"
hatch build
Version Validation
The env source validates versions against PEP 440:
# Valid versions
export VERSION="1.2.3"
export VERSION="1.0.0a1"
export VERSION="2023.12.1"
export VERSION="1.0.0+build.123"
# Invalid versions (will error)
export VERSION="v1.2.3" # No 'v' prefix
export VERSION="1.2" # Incomplete
export VERSION="latest" # Not a version
Automation Examples
Automatic Versioning Script
#!/bin/bash
# scripts/auto-version.sh
# Determine version based on git state
if git describe --exact-match --tags HEAD 2>/dev/null; then
# On a tag
VERSION=$(git describe --exact-match --tags HEAD | sed 's/^v//')
elif [ -n "$CI_COMMIT_SHA" ]; then
# In CI, use commit SHA
VERSION="0.0.0+ci.$(echo $CI_COMMIT_SHA | cut -c1-8)"
else
# Local development
VERSION="0.0.0+dev.$(git rev-parse --short HEAD)"
fi
export PACKAGE_VERSION="$VERSION"
echo "Building version: $VERSION"
hatch build
Pre-commit Hook
Ensure version is set before commits:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: check-version-env
name: Check VERSION environment variable
entry: sh -c 'test -n "$VERSION" || (echo "VERSION not set" && exit 1)'
language: system
pass_filenames: false
Multi-Version Builds
Build multiple versions in CI:
# .github/workflows/multi-version.yml
name: Multi-Version Build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
version: ["1.0.0", "1.1.0", "2.0.0-beta"]
steps:
- uses: actions/checkout@v3
- name: Build version ${{ matrix.version }}
env:
PACKAGE_VERSION: ${{ matrix.version }}
run: |
pip install hatch
hatch build
mkdir -p artifacts/${{ matrix.version }}
mv dist/* artifacts/${{ matrix.version }}/
- uses: actions/upload-artifact@v3
with:
name: packages
path: artifacts/
Kubernetes Integration
ConfigMap Version
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
VERSION: "1.2.3"
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
image: my-app:latest
envFrom:
- configMapRef:
name: app-config
Helm Chart
# helm/values.yaml
version: "1.2.3"
# helm/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: { { .Release.Name } }
spec:
template:
spec:
containers:
- name: app
env:
- name: PACKAGE_VERSION
value: { { .Values.version | quote } }
Limitations
Read-Only Source
The env source doesn't support setting versions:
$ hatch version patch
Error: The environment version source does not support setting the version
Workaround: Update the environment variable externally:
# Update version
export VERSION="1.2.4"
# Verify
hatch version
# Output: 1.2.4
No Version Bumping
Unlike file-based sources, env source can't bump versions:
# This won't work
$ hatch version minor
Error: Cannot bump version with environment source
Solution: Implement version bumping in your CI/CD:
#!/bin/bash
# Bump version script
current_version="${VERSION:-0.0.0}"
IFS='.' read -ra parts <<< "$current_version"
parts[2]=$((parts[2] + 1)) # Bump patch
new_version="${parts[0]}.${parts[1]}.${parts[2]}"
export VERSION="$new_version"
Best Practices
1. Always Provide Default Values
Prevent build failures by configuring sensible defaults:
[tool.hatch.version]
source = "env"
variable = "VERSION"
default = "0.0.0+unknown"
2. Document Required Variable Names
Clearly specify which environment variables are needed:
# pyproject.toml
[tool.hatch.version]
source = "env"
variable = "PACKAGE_VERSION" # Set by CI/CD pipeline
default = "0.0.0+dev" # Used in local development
3. Implement CI Validation
Add CI/CD checks to ensure version format compliance:
# .github/workflows/check.yml
- name: Validate version
run: |
python -c "
import os
from packaging.version import Version, InvalidVersion
try:
v = Version(os.environ['VERSION'])
print(f'Valid version: {v}')
except InvalidVersion:
print(f'Invalid version: {os.environ.get('VERSION', 'not set')}')
exit(1)
"
4. Use Descriptive and Project-Specific Variable Names
Choose clear names that identify the project:
# Good
variable = "MY_PACKAGE_VERSION"
variable = "WIDGET_LIB_VERSION"
# Avoid generic names
variable = "VERSION" # Too generic
variable = "V" # Too short
Troubleshooting
Variable Not Set
$ hatch version
Error: Environment variable 'VERSION' is not set
Solutions:
- Set the variable:
export VERSION="1.2.3" - Add a default:
default = "0.0.0+dev" - Check variable name spelling
Invalid Version Format
$ export VERSION="v1.2.3"
$ hatch version
Error: Invalid version 'v1.2.3'
Fix: Remove invalid characters:
export VERSION="1.2.3" # No 'v' prefix
Variable Not Updating
# Shell doesn't see updated variable
$ VERSION=1.2.3
$ hatch version
Error: Environment variable 'VERSION' is not set
Fix: Export the variable:
export VERSION=1.2.3
Integration with Other Tools
Poetry Migration
# From Poetry with poetry-dynamic-versioning
[tool.poetry-dynamic-versioning]
enable = true
# To Hatchling
[tool.hatch.version]
source = "env"
variable = "VERSION"
Setuptools-scm Migration
# From setuptools-scm
[tool.setuptools_scm]
write_to = "src/_version.py"
# To Hatchling with env source
[tool.hatch.version]
source = "env"
variable = "SETUPTOOLS_SCM_PRETEND_VERSION"
See Also
- Dynamic Version Sources Overview
- Code Version Source - For programmatic version logic
- Regex Version Source - For file-based versions
- CI/CD Best Practices - Advanced CI/CD patterns