Automatic, configurable CalVer versioning for setuptools-scm — no manual bumping required
Project description
calver-scm
Automatic, date-based versioning for Python projects — powered by your Git history.
calver-scm is a setuptools-scm plugin that generates CalVer version strings directly from your Git tags and commit history. No __version__ files to maintain, no manual bumping — just tag and go.
Versions look like this:
2026.04.0 ← clean tag, April 2026, patch 0
2026.04.1.dev3 ← 3 commits after that tag, still April, patch incremented
2026.05.0.dev3 ← 3 commits after an old tag, month rolled over, patch reset
2026.04.15.0 ← day mode: clean tag on the 15th
2026.04.15.1.dev2 ← day mode: 2 commits after a tag on the same day
2026.04.0.dev12 ← no tag yet, 12 commits in
Installation
pip install calver-scm
Then wire it into your pyproject.toml. Pick the setup that matches your build backend:
With setuptools
[build-system]
requires = ["setuptools", "setuptools-scm", "calver-scm"]
build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
version_scheme = "calver_scm"
local_scheme = "dirty-tag"
With hatch-vcs (recommended)
hatch-vcs is a Hatchling plugin that delegates versioning to setuptools-scm under the hood. It's a great pairing with calver-scm — Hatchling handles your build, hatch-vcs reads the Git tags, and calver-scm turns them into CalVer strings.
[build-system]
requires = ["hatchling", "hatch-vcs", "calver-scm"]
build-backend = "hatchling.build"
[project]
dynamic = ["version"]
[tool.hatch.version]
source = "vcs"
[tool.hatch.version.raw-options]
version_scheme = "calver_scm"
local_scheme = "dirty-tag"
The raw-options table passes arguments directly through hatch-vcs to setuptools-scm, so calver_scm is picked up exactly as if you had configured it directly.
Optionally, you can have hatch-vcs write the resolved version to a _version.py file at build time — handy if you want your_package.__version__ to be available at runtime:
[tool.hatch.build.hooks.vcs]
version-file = "src/your_package/_version.py"
Then in your __init__.py:
from ._version import __version__
That's it. Your project will now version itself automatically on every build.
How versions are generated
Every version follows the pattern:
YYYY.MM[.DD].PATCH[.devN][+dirty]
| Situation | Example |
|---|---|
| Clean tag, month mode | 2026.04.0 |
| Commits after tag, same month | 2026.04.1.dev3 |
| Commits after tag, previous month's tag | 2026.05.0.dev3 |
| Clean tag, day mode | 2026.04.15.0 |
| Dirty working tree | 2026.04.0+dirty |
| No tag yet | 2026.04.0.dev12 |
The patch segment increments automatically from your last tag within the current period, and resets to 0 whenever the month (or day, in day mode) rolls over.
Pre-release, post-release, and local tags
If your Git tag carries any PEP 440 suffix, calver-scm preserves it in the
generated version and normalises it to its
PEP 440 canonical form.
Pre-release suffixes (a, b, rc)
Tag any of the recognised PEP 440 aliases and it is normalised automatically:
| Tag suffix | Canonical output |
|---|---|
alpha / a |
a |
beta / b |
b |
preview / pre / c / rc |
rc |
v2026.04.0a1 → 2026.04.0a1
v2026.04.0beta2 → 2026.04.0b2
v2026.04.0pre1 → 2026.04.0rc1
v2026.04.0rc1 → 2026.04.0rc1
Post-release suffixes (post)
| Tag suffix | Canonical output |
|---|---|
-rN / revN |
postN |
post-N / postN |
postN |
v2026.04.0-r1 → 2026.04.0.post1
v2026.04.0.rev2 → 2026.04.0.post2
v2026.04.0.post-3 → 2026.04.0.post3
v2026.04.0.post3 → 2026.04.0.post3
Dev suffixes (dev)
| Tag suffix | Canonical output |
|---|---|
dev (bare) |
dev0 |
dev-N / devN |
devN |
v2026.04.0.dev → 2026.04.0.dev0
v2026.04.0.dev-4 → 2026.04.0.dev4
v2026.04.0.dev4 → 2026.04.0.dev4
Local version segment (+)
A +local segment on a tag is preserved as-is. PEP 440 normalises
underscores to dots within local identifiers:
v2026.04.0+abc → 2026.04.0+abc
v2026.04.0+linux.x86_64 → 2026.04.0+linux.x86.64
Note: The
+localsegment on a tag is distinct from the+dirtysuffix that setuptools-scm appends for uncommitted working-tree changes. See Local scheme for how to control that behaviour.
Combining suffixes
All segments can be combined and are each normalised independently:
v2026.04.0rc1.post2.dev3+local → 2026.04.0rc1.post2.dev3+local
Configuration
Add a [tool.calver_scm] section to pyproject.toml to customise behaviour. All fields are optional — the defaults shown below are sensible for most projects.
[tool.calver_scm]
mode = "month" # "month" | "day"
patch = true # auto-increment patch within a period
fallback = "dev" # "dev" | "date" — when no tag exists
tag_prefix = "v" # prefix stripped when reading tags
mode
Controls the time granularity of the version base.
"month"— base isYYYY.MM(e.g.2026.04)"day"— base isYYYY.MM.DD(e.g.2026.04.15)
# Month mode (default) — good for most projects
[tool.calver_scm]
mode = "month"
# → 2026.04.0, 2026.04.1.dev3, 2026.05.0.dev1 …
# Day mode — useful for projects that release frequently
[tool.calver_scm]
mode = "day"
# → 2026.04.15.0, 2026.04.15.1.dev2, 2026.04.16.0.dev1 …
patch
When true, the patch number increments from the last tag within the same period. When false, patch is always 0 and only the .devN distance distinguishes pre-release builds.
# patch = true (default) — each build within a period gets a unique patch number
[tool.calver_scm]
patch = true
# tag v2026.04.2, then 3 commits later → 2026.04.3.dev3
# patch = false — patch stays 0, only devN changes
[tool.calver_scm]
patch = false
# tag v2026.04.2, then 3 commits later → 2026.04.0.dev3
fallback
Controls what happens when no tag exists yet in the repository.
"dev"— emitsYYYY.MM.0.devNwhere N is the total commit count (default)"date"— emitsYYYY.MM.0with no dev segment, useful for initial releases
# fallback = "dev" (default) — makes it clear this is a pre-release build
[tool.calver_scm]
fallback = "dev"
# 12 commits, no tag yet → 2026.04.0.dev12
# fallback = "date" — emits a clean version even before the first tag
[tool.calver_scm]
fallback = "date"
# 12 commits, no tag yet → 2026.04.0
tag_prefix
The string that must prefix a tag for it to be recognised as a CalVer tag. Set to "" if your tags have no prefix.
# tag_prefix = "v" (default) — tags like v2026.04.0
[tool.calver_scm]
tag_prefix = "v"
# No prefix — tags like 2026.04.0
[tool.calver_scm]
tag_prefix = ""
# Custom prefix — tags like release-2026.04.0
[tool.calver_scm]
tag_prefix = "release-"
Environment variable overrides
Every option can be overridden at build time without touching pyproject.toml — handy for CI pipelines.
| Variable | Equivalent option |
|---|---|
SCM_CALVER_MODE |
mode |
SCM_CALVER_PATCH |
patch (true / false) |
SCM_CALVER_FALLBACK |
fallback |
SCM_CALVER_TAG_PREFIX |
tag_prefix |
Environment variables take precedence over pyproject.toml.
Local scheme
calver_scm controls the public version segment only. The +dirty suffix is handled separately by setuptools-scm's built-in local schemes. Pick whichever suits you:
[tool.setuptools_scm]
version_scheme = "calver_scm"
local_scheme = "dirty-tag" # +dirty on uncommitted changes (recommended)
# local_scheme = "no-local-version" # suppress the local segment entirely
# local_scheme = "node-and-date" # +g1a2b3c4.d20260415 (setuptools-scm default)
Requirements
- Python ≥ 3.10
- setuptools-scm ≥ 9.2.2
tomli≥ 2.0.0 on Python 3.10 (the stdlibtomllibis used on 3.11+)
Contributing
git clone https://github.com/samcorky/calver-scm
cd calver-scm
uv sync --group dev
pre-commit install
pytest
Pull requests and issues are welcome!
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file calver_scm-1.1.0.tar.gz.
File metadata
- Download URL: calver_scm-1.1.0.tar.gz
- Upload date:
- Size: 47.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
96b349cf6457f3297f7fab85b7f2e2e0f7017d8b55451e8a5364a30f0a64dfea
|
|
| MD5 |
4a5de98710aabe31710f89a2fac1ef92
|
|
| BLAKE2b-256 |
e43fac5ae453cb197e151ca14d5c9b4329601df74032697901bbc53ef36c9f06
|
File details
Details for the file calver_scm-1.1.0-py3-none-any.whl.
File metadata
- Download URL: calver_scm-1.1.0-py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7478c9049349689607fcef1fc4e5392c86e5748046e9950db6f1c5a734f6e4ef
|
|
| MD5 |
333e64e6ca06f07d306dc59a212e3ba8
|
|
| BLAKE2b-256 |
0310ed0b3cea3b41a4511289897574c1758fa1901024cb470b411151fb29f372
|