Version Management for spotforecast2-safe

Overview

As a safety-critical system, spotforecast2-safe implements strict version control to ensure auditability and reproducibility across releases. This document outlines the version management strategy for MLOps engineers.

Design Principle: Single Source of Truth

Version is defined once, in pyproject.toml

[project]
name = "spotforecast2-safe"
version = "0.0.5"  # ← Single source of truth

All other files (e.g., MODEL_CARD.md, documentation) reference this version programmatically or are automatically synchronized.

Architecture

1. Version Definition Layer (pyproject.toml)

  • Contains the canonical version
  • Used by pip, uv, and build systems
  • Updated during release workflows

2. Version Access Layer (__init__.py)

  • Exposes version via __version__ attribute

  • Uses importlib.metadata.version() (runtime dynamic)

  • Fallback for development environments

  • Accessible programmatically:

    import spotforecast2_safe
    print(spotforecast2_safe.__version__)  # → "0.0.5"

3. Version Synchronization Layer (scripts/update_version.py)

  • Sets the release version in pyproject.toml and propagates it to MODEL_CARD.md
  • Guarantees consistency across files
  • Invoked by make release, by the pre-commit hook, or by hand

4. Version Verification Layer (Pre-commit hook, --verify)

  • Validates version consistency
  • Prevents accidental mismatches
  • Supports audit compliance

The rendered documentation is deliberately outside this chain. docs/index.qmd writes {{< var version >}}, which resolves from _variables.yml; that file is regenerated from pyproject.toml by docs/pre-render.py on every quarto render. The documentation version cannot drift, so nothing needs to synchronize it.

Workflow

For release engineers

1. During development

The version stays at the current release version in pyproject.toml. No manual updates needed.

2. Cutting a release

The version number is chosen by the maintainer — nothing derives it from commit messages. The release target in the Makefile performs the bump as its second step, after the verify gate:

make release VERSION=25.3.0

This runs scripts/update_version.py --set 25.3.0, which writes the version into pyproject.toml and synchronizes MODEL_CARD.md (the version table, the current-release CPE, the citation, and the lifecycle sentence). It then generates the CHANGELOG.md section and stops, so the maintainer can review both before committing.

To perform only the bump, outside a release:

uv run python scripts/update_version.py --set 25.3.0            # set and synchronize
uv run python scripts/update_version.py --set 25.3.0 --dry-run  # preview the changes
uv run python scripts/update_version.py --verify                # check consistency

See RELEASING.md for the complete release runbook.

For Developers

I want to check the current version:

Option A: From Python

import spotforecast2_safe
print(spotforecast2_safe.__version__)

Option B: From CLI

python scripts/update_version.py --verify

Option C: From pyproject.toml

grep 'version = ' pyproject.toml

I want to verify everything is in sync:

python scripts/update_version.py --verify

Exit code: 0 = version consistent, 1 = inconsistency detected

Script Reference

scripts/update_version.py

Purpose: Set the release version and synchronize it across project artifacts.

Usage:

# Set the release version (what `make release` calls)
uv run python scripts/update_version.py --set 25.3.0

# Normal: re-synchronize from the version already in pyproject.toml
uv run python scripts/update_version.py

# Dry-run: Show changes without modifying files
uv run python scripts/update_version.py --dry-run

# Verify: Only check consistency (no modifications)
uv run python scripts/update_version.py --verify

Output:

======================================================================
spotforecast2-safe: Version Synchronization Script
======================================================================

📋 Version Consistency Check:
  pyproject.toml:  25.2.0
  MODEL_CARD.md:   25.2.0
✓ Versions are in sync!

Files Synchronized

  1. pyproject.toml - Primary source
  2. MODEL_CARD.md - Model/Method card (for EU AI Act compliance)
  3. src/spotforecast2_safe/__init__.py - Package metadata (read dynamically via importlib.metadata)
  4. _variables.yml - Quarto documentation variables (regenerated by docs/pre-render.py at render time, not by this script)

Safety & Compliance Features

✅ Audit Trail

# Git history preserves all version changes
git log --oneline -- pyproject.toml

✅ Consistency Guarantees

  • Version synchronization script prevents mismatches
  • Pre-commit hooks enforce synchronization
  • make release refuses to proceed on a dirty tree, an existing tag, or a malformed version

✅ EU AI Act Compliance

  • Version is documented in MODEL_CARD.md (Article 13 - Transparency)
  • Version history is traceable in git
  • Releases are tagged with version information

✅ Reproducibility

  • Each version pins exact dependency versions
  • uv lock creates lockfile for determinism
  • Users can reproduce results with spotforecast2_safe==0.0.5

Release Integration

There is no continuous-integration service. Versioning is one step of the local release pipeline defined in the Makefile:

make release VERSION=25.3.0     # verify gate, bump, synchronize, generate CHANGELOG (stops here)
# review the diff, then:
make release-commit VERSION=25.3.0   # commit, tag v25.3.0, push
make build sbom                 # wheel + sdist in dist/, SBOM in artifacts/
make publish                    # twine check + twine upload
make doc-publish                # render and rsync the site

make release runs make verify before touching the version, so a release cannot be staged from a tree that fails the tests, the lint checks, the SPDX audit, or the API-reference sync.

The full runbook, including what the retired GitHub Actions pipeline used to provide and no longer does, is in RELEASING.md.

Troubleshooting

Issue: Version mismatch detected

⚠ Version mismatch detected!
  pyproject.toml:  0.0.6
  MODEL_CARD.md:   0.0.5

Solution: Run synchronization script

python scripts/update_version.py

Issue: Pre-commit hook prevents commit

This is intentional! The hook detected a version mismatch.

Solution:

# Let the hook automatically fix it
pre-commit run --all-files

# Then commit normally
git add .
git commit -m "chore: synchronize versions"

Issue: importlib.metadata not found

This should not happen in Python 3.8+. If it does:

  1. Check Python version: python --version
  2. Ensure package is installed: uv pip install -e .
  3. Fallback version (0.0.5) will be used automatically

Best Practices

✅ Do

  • Change the version only through update_version.py --set, which keeps pyproject.toml the single source of truth
  • Use make release so the bump, the changelog and the verify gate stay in one sequence
  • Install the pre-commit hooks
  • Read the generated CHANGELOG.md section before running make release-commit

❌ Don’t

  • Manually edit the version in MODEL_CARD.md
  • Hand-edit _variables.yml — it is regenerated on every render
  • Skip make verify before a release
  • Commit version mismatches to the release branch

Versioning Scheme

spotforecast2-safe uses Semantic Versioning (MAJOR.MINOR.PATCH):

  • Major (0.X.X): Breaking API changes (rare for a data transformation library)
  • Minor (X.Y.X): New features, improvements (backward compatible)
  • Patch (X.Y.Z): Bug fixes, documentation updates

Example: - 0.0.10.0.2: Patch - bug fix - 0.0.50.1.0: Minor - new feature - 0.X.X1.0.0: Major - breaking change

References