setuptools-scm review
setuptools-scm 10.2.1 calculates a Python distribution version from repository tags while setuptools builds the artifact. A release tag becomes the clean version; commits after it can become a PEP 440 development version containing commit distance and revision data. Installing the tool also registers a file finder that feeds version-controlled files into source distributions. The current patch removes `scm_version.json` and `scm_file_list.json` from wheel `.dist-info`, while retaining them in sdists where metadata-free fallback discovery needs them. This is build tooling, not a runtime version service, and it depends on trustworthy Git, Mercurial, or supported VCS metadata being present during the build.
setuptools-scm 10.2.1 installed in 0.2 seconds and occupied 5 MB across 4 packages, with zero audit findings in our sandbox. Use it for tagged setuptools releases only when CI supplies trustworthy history and inspects sdists; choose the build backend's native plugin or an external version when `.git` is routinely absent.
We installed it
| Install | ✓ · 0.2s | 4 packages on disk · 5 MB |
| Import | ✓ | import setuptools_scm in 0.31s · pure Python · py.typed · requires Python >=3.8 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does setuptools-scm install cleanly?
Yes. In a fresh container with an empty cache, pip install setuptools-scm finished in 0.2s, leaving 4 packages and 5 MB on disk. pip-audit reported no known vulnerabilities.
What does setuptools-scm need to run?
Python >=3.8, and nothing compiled: it is pure Python. In our run import setuptools_scm succeeded in 0.31s, and the package ships py.typed for type checkers.
setuptools-scm or hatch-vcs: which should you use?
hatch-vcs: Hatchling owns the build and VCS versioning should remain in that backend. setuptools-scm 10.2.1 installed in 0.2 seconds and occupied 5 MB across 4 packages, with zero audit findings in our sandbox.
When should you not use setuptools-scm?
Builds routinely receive shallow history, exported trees, or Docker contexts without .git. Those inputs need an external version override or archive metadata.
Use it if
- A setuptools project publishes from tags and wants wheel metadata to use the same version source.
- Artifacts built between releases need distinct, sortable PEP 440 development versions.
- The sdist should follow tracked files, with a short exclusion list instead of a full inclusion manifest.
- Each package in a monorepo has a tag prefix that must be selected and stripped before parsing.
- Builds routinely receive shallow history, exported trees, or Docker contexts without `.git`. Those inputs need an external version override or archive metadata.
- Hatchling or another backend already has a native VCS plugin. Adding setuptools solely for version calculation mixes build stacks.
- Nearly every tracked fixture and screenshot must stay out of the sdist. The automatic finder makes `MANIFEST.in` exclusions an ongoing burden.
- Dirty builds and local version segments are forbidden, but CI cannot enforce the configured scheme before publishing.
- Code needs `__version__` when run from an uninstalled checkout before any build. Distribution metadata is absent there, and a generated module adds another workflow.
Setup reality
We installed setuptools-scm 10.2.1 in a fresh Python 3.12 Bookworm container in 0.2 seconds. Four packages used 5 MB, import setuptools_scm took 0.31 seconds, and pip-audit found zero known vulnerabilities. The package declares 6 direct dependencies and supports Python 3.8 or newer. It is pure Python, ships py.typed, and declares MIT. These numbers describe the build helper; a normal installed application should not need it at runtime.
Put the package under [build-system].requires, declare the project version dynamic, and add [tool.setuptools_scm]. Sectionless activation only works with the documented simple extra because plain dependency-triggered activation was removed in 9.2. Runtime code should read importlib.metadata.version() or import a generated version module. Calling setuptools_scm.get_version() from application code makes production depend on repository state that is often absent.
A correct version needs reachable tags and enough history to calculate distance. Shallow CI checkouts, release archives, and Docker contexts often omit one or both. Fetch complete tags for releases. When another build system already knows the version, pass the distribution-scoped SETUPTOOLS_SCM_PRETEND_VERSION_FOR_* variable. A fallback_version prevents failure but can silently stamp the wrong value, so use it only where downstream packaging owns that decision.
Presence in build requirements activates the SCM file finder, which includes tracked files in the sdist. Inspect the list with python -m setuptools_scm ls, prune unwanted paths in MANIFEST.in, then open the built archive before publishing. Ignore generated version modules in Git; rewriting a tracked module dirties the checkout and may alter its own calculated version. In 10.2.1, the 2 SCM JSON metadata files remain in sdists but are deliberately excluded from wheels.
Patterns
Configure dynamic versioning enable-explicit-versioning
[build-system]
requires = ["setuptools>=80", "setuptools-scm>=10"]
build-backend = "setuptools.build_meta"
[project]
name = "sample-package"
dynamic = ["version"]
[tool.setuptools_scm]A dynamic version cannot coexist with a static `project.version`. Even an empty tool table explicitly activates setuptools-scm.
Use sectionless simple activation enable-simple-versioning
[build-system]
requires = ["setuptools>=80", "setuptools-scm[simple]>=10"]
build-backend = "setuptools.build_meta"
[project]
name = "sample-package"
dynamic = ["version"]Sectionless setup depends on the `simple` extra. Since 9.2, merely listing the ordinary package no longer activates version inference.
Print the version before building inspect-computed-version
python -m setuptools_scm
SETUPTOOLS_SCM_DEBUG=1 python -m setuptools_scmThe debug flag prints how the repository and tag were selected. Compare this output in CI when the calculated version differs from a workstation.
Inspect automatic file discovery list-sdist-files
python -m setuptools_scm ls
python -m build --sdist
tar -tzf dist/sample_package-*.tar.gz`ls` displays files found through source control. Setuptools applies `MANIFEST.in` pruning after that discovery step.
Exclude tracked development data prune-source-distribution
# MANIFEST.in
prune tests/fixtures/large-corpus
prune docs/_build
exclude .pre-commit-config.yaml
global-exclude *.pycThe file finder activates with the build dependency. Exclude tracked paths through setuptools manifest rules because the tool table has no off switch.
Write a version module during builds generate-version-module
[tool.setuptools_scm]
version_file = "src/sample_package/_version.py"
# .gitignore
# src/sample_package/_version.pyKeep `_version.py` out of source control. Rewriting a tracked generated file dirties the checkout and can change the version being written.
Read distribution metadata at runtime read-installed-version
from importlib.metadata import PackageNotFoundError, version
try:
__version__ = version('sample-package')
except PackageNotFoundError:
__version__ = '0+uninstalled'`version()` expects the installed distribution name, which may differ from the import name. It reads wheel metadata without needing `.git` at runtime.
Provide release history in GitHub Actions fetch-tags-in-actions
- uses: actions/checkout@v6
with:
fetch-depth: 0
- run: python -m build`fetch-depth: 0` brings tags and ancestry into the runner. A shallow clone may lack either the release tag or the distance from it.
Override a history-free build supply-known-build-version
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_SAMPLE_PACKAGE=2.4.0 \
python -m buildThe scoped variable uppercases the distribution name and replaces separators with underscores. It avoids giving every package in a monorepo one version.
Reject dirty release builds configure-release-scheme
[tool.setuptools_scm]
local_scheme = "no-local-version-strict"`no-local-version-strict` rejects a dirty tree while omitting local metadata. Run it only in a release path that treats any uncommitted change as an error.
Select one package's tag namespace filter-monorepo-tags
[tool.setuptools_scm]
root = "../.."
relative_to = "pyproject.toml"
[tool.setuptools_scm.tag]
prefix = "api-v"
strict = trueThe prefix selects this package's tags and is removed before version parsing. `root` is resolved relative to the configured project file.
Embed version hints in git archives support-git-archives
# .git_archival.txt
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
# .gitattributes
.git_archival.txt export-substBoth archive files must exist in the tagged commit. Placeholder substitution happens during `git archive`, not in an ordinary working tree.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| hatch-vcs | PyPI | Pick it when Hatchling owns the build and VCS versioning should remain in that backend. |
| versioningit | PyPI | Pick it for configurable formatting, file replacement, and source-archive steps. |
| dunamai | PyPI | Pick its library or CLI when version derivation must stay independent of the Python build backend. |
More cli & tooling guides
commander · chalk · typescript · esbuild · yargs · click · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

